2020-10-05 22:30:06 +08:00
import bpy , mathutils
2020-10-07 22:25:02 +08:00
import os , math
from bpy_extras import io_utils , node_shader_utils
2020-10-08 21:19:06 +08:00
# from bpy_extras.io_utils import unpack_list
2020-10-07 22:25:02 +08:00
from bpy_extras . image_utils import load_image
2020-10-05 22:30:06 +08:00
from . import utils , config
class BALLANCE_OT_add_floor ( bpy . types . Operator ) :
""" Add Ballance floor """
bl_idname = " ballance.add_floor "
bl_label = " Add floor "
bl_options = { ' UNDO ' }
floor_type : bpy . props . EnumProperty (
name = " Type " ,
description = " Floor type " ,
items = tuple ( ( x , x , " " ) for x in config . floor_block_dict . keys ( ) ) ,
)
expand_length_1 : bpy . props . IntProperty (
name = " D1 length " ,
description = " The length of expand direction 1 " ,
2020-10-06 12:57:25 +08:00
min = 0 ,
2020-10-05 22:30:06 +08:00
default = 0 ,
)
expand_length_2 : bpy . props . IntProperty (
name = " D2 length " ,
description = " The length of expand direction 2 " ,
2020-10-06 12:57:25 +08:00
min = 0 ,
2020-10-05 22:30:06 +08:00
default = 0 ,
)
height_multiplier : bpy . props . FloatProperty (
name = " Height " ,
description = " The multiplier for height. Default height is 5 " ,
2020-10-06 12:57:25 +08:00
min = 0.0 ,
2020-10-05 22:30:06 +08:00
default = 1.0 ,
)
use_2d_top : bpy . props . BoolProperty (
name = " Top side "
)
use_2d_right : bpy . props . BoolProperty (
name = " Right side "
)
use_2d_bottom : bpy . props . BoolProperty (
name = " Bottom side "
)
use_2d_left : bpy . props . BoolProperty (
name = " Left side "
)
use_3d_top : bpy . props . BoolProperty (
name = " Top face "
)
use_3d_bottom : bpy . props . BoolProperty (
name = " Bottom face "
)
2020-10-07 15:49:12 +08:00
previous_floor_type = ' '
2020-10-06 12:57:25 +08:00
@classmethod
def poll ( self , context ) :
prefs = bpy . context . preferences . addons [ __package__ ] . preferences
return os . path . isdir ( prefs . external_folder )
2020-10-05 22:30:06 +08:00
def execute ( self , context ) :
2020-10-08 13:07:43 +08:00
# load mesh
objmesh = bpy . data . meshes . new ( ' done_ ' )
if self . floor_type in config . floor_basic_block_list :
load_basic_floor (
objmesh ,
self . floor_type ,
2020-10-08 17:12:38 +08:00
' R0 ' ,
2020-10-08 13:07:43 +08:00
self . height_multiplier ,
self . expand_length_1 ,
self . expand_length_2 ,
( self . use_2d_top ,
self . use_2d_right ,
self . use_2d_bottom ,
self . use_2d_left ,
self . use_3d_top ,
self . use_3d_bottom ) ,
( 0.0 , 0.0 ) )
elif self . floor_type in config . floor_derived_block_list :
load_derived_floor (
objmesh ,
2020-10-08 21:19:06 +08:00
self . floor_type ,
2020-10-08 13:07:43 +08:00
self . height_multiplier ,
self . expand_length_1 ,
self . expand_length_2 ,
( self . use_2d_top ,
self . use_2d_right ,
self . use_2d_bottom ,
self . use_2d_left ,
self . use_3d_top ,
self . use_3d_bottom ) )
2020-10-08 21:19:06 +08:00
# normalization mesh
objmesh . validate ( clean_customdata = False )
objmesh . update ( calc_edges = False , calc_edges_loose = False )
2020-10-08 13:07:43 +08:00
# create object and link it
obj = bpy . data . objects . new ( ' A_Floor_BMERevenge_ ' , objmesh )
utils . AddSceneAndMove2Cursor ( obj )
2020-10-05 22:30:06 +08:00
return { ' FINISHED ' }
def invoke ( self , context , event ) :
wm = context . window_manager
return wm . invoke_props_dialog ( self )
def draw ( self , context ) :
2020-10-07 15:49:12 +08:00
# get floor prototype
floor_prototype = config . floor_block_dict [ self . floor_type ]
# try sync default value
if self . previous_floor_type != self . floor_type :
self . previous_floor_type = self . floor_type
default_sides = floor_prototype [ ' DefaultSideConfig ' ]
self . use_2d_top = default_sides [ ' UseTwoDTop ' ]
self . use_2d_right = default_sides [ ' UseTwoDRight ' ]
self . use_2d_bottom = default_sides [ ' UseTwoDBottom ' ]
self . use_2d_left = default_sides [ ' UseTwoDLeft ' ]
self . use_3d_top = default_sides [ ' UseThreeDTop ' ]
self . use_3d_bottom = default_sides [ ' UseThreeDBottom ' ]
# show property
2020-10-05 22:30:06 +08:00
layout = self . layout
col = layout . column ( )
col . label ( text = " Basic param " )
col . prop ( self , " floor_type " )
2020-10-06 12:57:25 +08:00
col . prop ( self , " height_multiplier " )
col . separator ( )
col . label ( text = " Expand " )
2020-10-07 15:49:12 +08:00
if floor_prototype [ ' ExpandType ' ] == ' Column ' or floor_prototype [ ' ExpandType ' ] == ' Freedom ' :
col . prop ( self , " expand_length_1 " )
if floor_prototype [ ' ExpandType ' ] == ' Freedom ' :
col . prop ( self , " expand_length_2 " )
col . label ( text = " Unit size: " + floor_prototype [ ' UnitSize ' ] )
col . label ( text = " Expand mode: " + floor_prototype [ ' ExpandType ' ] )
grids = col . grid_flow ( row_major = True , columns = 3 )
2020-10-06 12:57:25 +08:00
grids . separator ( )
2020-10-07 15:49:12 +08:00
grids . label ( text = config . floor_expand_direction_map [ floor_prototype [ ' InitColumnDirection ' ] ] [ floor_prototype [ ' ExpandType ' ] ] [ 0 ] )
2020-10-06 12:57:25 +08:00
grids . separator ( )
2020-10-07 15:49:12 +08:00
grids . label ( text = config . floor_expand_direction_map [ floor_prototype [ ' InitColumnDirection ' ] ] [ floor_prototype [ ' ExpandType ' ] ] [ 3 ] )
2020-10-06 12:57:25 +08:00
grids . template_icon ( icon_value = config . blenderIcon_floor_dict [ self . floor_type ] )
2020-10-07 15:49:12 +08:00
grids . label ( text = config . floor_expand_direction_map [ floor_prototype [ ' InitColumnDirection ' ] ] [ floor_prototype [ ' ExpandType ' ] ] [ 1 ] )
2020-10-06 12:57:25 +08:00
grids . separator ( )
2020-10-07 15:49:12 +08:00
grids . label ( text = config . floor_expand_direction_map [ floor_prototype [ ' InitColumnDirection ' ] ] [ floor_prototype [ ' ExpandType ' ] ] [ 2 ] )
2020-10-06 12:57:25 +08:00
grids . separator ( )
2020-10-05 22:30:06 +08:00
col . separator ( )
col . label ( text = " Faces " )
row = col . row ( )
row . prop ( self , " use_3d_top " )
row . prop ( self , " use_3d_bottom " )
col . separator ( )
col . label ( text = " Sides " )
2020-10-07 15:49:12 +08:00
grids = col . grid_flow ( row_major = True , columns = 3 )
2020-10-06 12:57:25 +08:00
grids . separator ( )
grids . prop ( self , " use_2d_top " )
grids . separator ( )
grids . prop ( self , " use_2d_left " )
grids . template_icon ( icon_value = config . blenderIcon_floor_dict [ self . floor_type ] )
grids . prop ( self , " use_2d_right " )
grids . separator ( )
grids . prop ( self , " use_2d_bottom " )
grids . separator ( )
2020-10-05 22:30:06 +08:00
2020-10-07 22:25:02 +08:00
def face_fallback ( normal_face , expand_face , height ) :
if expand_face == None :
return normal_face
if height < = 1.0 :
return normal_face
else :
return expand_face
2020-10-05 22:30:06 +08:00
2020-10-07 22:25:02 +08:00
def create_or_get_material ( material_name ) :
# WARNING: this code is shared with bm_import_export
deconflict_name = " BMERevenge_ " + material_name
try :
m = bpy . data . materials [ deconflict_name ]
except :
# it is not existed, we need create a new one
m = bpy . data . materials . new ( deconflict_name )
# we need init it.
# load texture first
externalTextureFolder = bpy . context . preferences . addons [ __package__ ] . preferences . external_folder
txur = load_image ( config . floor_texture_corresponding_map [ material_name ] , externalTextureFolder , check_existing = False ) # force reload, we don't want it is shared with normal material
# create material and link texture
m . use_nodes = True
for node in m . node_tree . nodes :
m . node_tree . nodes . remove ( node )
bnode = m . node_tree . nodes . new ( type = " ShaderNodeBsdfPrincipled " )
mnode = m . node_tree . nodes . new ( type = " ShaderNodeOutputMaterial " )
m . node_tree . links . new ( bnode . outputs [ 0 ] , mnode . inputs [ 0 ] )
inode = m . node_tree . nodes . new ( type = " ShaderNodeTexImage " )
inode . image = txur
m . node_tree . links . new ( inode . outputs [ 0 ] , bnode . inputs [ 0 ] )
# write custom property
# WARNING: this data is shared with BallanceVirtoolsPlugin - mapping_BM.cpp - fix_blender_texture
m [ ' virtools-ambient ' ] = ( 0.0 , 0.0 , 0.0 )
m [ ' virtools-diffuse ' ] = ( 122 / 255.0 , 122 / 255.0 , 122 / 255.0 ) if material_name == ' FloorSide ' else ( 1.0 , 1.0 , 1.0 )
m [ ' virtools-specular ' ] = ( 0.0 , 0.0 , 0.0 ) if material_name == ' FloorSide ' else ( 80 / 255.0 , 80 / 255.0 , 80 / 255.0 )
m [ ' virtools-emissive ' ] = ( 104 / 255.0 , 104 / 255.0 , 104 / 255.0 ) if material_name == ' FloorSide ' else ( 0.0 , 0.0 , 0.0 )
m [ ' virtools-power ' ] = 0.0
return m
def solve_vec_data ( str_data , d1 , d2 , d3 , unit , unit_height ) :
2020-10-08 13:07:43 +08:00
sp = str_data . split ( ' ; ' )
sp_point = sp [ 0 ] . split ( ' , ' )
2020-10-07 22:25:02 +08:00
vec = [ float ( sp_point [ 0 ] ) , float ( sp_point [ 1 ] ) , float ( sp_point [ 2 ] ) ]
for i in range ( 3 ) :
symbol = sp [ i + 1 ]
if symbol == ' ' :
continue
factor = 1.0 if symbol [ 0 ] == ' + ' else - 1.0
p = symbol [ 1 : ]
if p == ' d1 ' :
vec [ i ] + = d1 * unit * factor
elif p == ' d2 ' :
vec [ i ] + = d2 * unit * factor
elif p == ' d3 ' :
vec [ i ] + = ( d3 - 1 ) * unit_height * factor
return vec
2020-10-08 13:07:43 +08:00
def rotate_translate_vec ( vec , rotation , unit , extra_translate ) :
2020-10-07 22:25:02 +08:00
vec [ 0 ] - = unit / 2
vec [ 1 ] - = unit / 2
if rotation == ' R0 ' :
coso = 1
sino = 0
elif rotation == ' R90 ' :
coso = 0
sino = 1
elif rotation == ' R180 ' :
coso = - 1
sino = 0
elif rotation == ' R270 ' :
coso = 0
sino = - 1
return (
2020-10-08 21:19:06 +08:00
coso * vec [ 0 ] - sino * vec [ 1 ] + unit / 2 + extra_translate [ 0 ] ,
sino * vec [ 0 ] + coso * vec [ 1 ] + unit / 2 + extra_translate [ 1 ] ,
2020-10-07 22:25:02 +08:00
vec [ 2 ]
)
def solve_uv_data ( str_data , d1 , d2 , d3 , unit ) :
2020-10-08 13:07:43 +08:00
sp = str_data . split ( ' ; ' )
sp_point = sp [ 0 ] . split ( ' , ' )
2020-10-07 22:25:02 +08:00
vec = [ float ( sp_point [ 0 ] ) , float ( sp_point [ 1 ] ) ]
for i in range ( 2 ) :
symbol = sp [ i + 1 ]
if symbol == ' ' :
continue
factor = 1.0 if symbol [ 0 ] == ' + ' else - 1.0
p = symbol [ 1 : ]
if p == ' d1 ' :
vec [ i ] + = d1 * unit * factor
elif p == ' d2 ' :
vec [ i ] + = d2 * unit * factor
elif p == ' d3 ' :
vec [ i ] + = ( d3 - 1 ) * unit * factor
return tuple ( vec )
def solve_normal_data ( point1 , point2 , point3 ) :
vector1 = (
point2 [ 0 ] - point1 [ 0 ] ,
point2 [ 1 ] - point1 [ 1 ] ,
point2 [ 2 ] - point1 [ 2 ]
)
vector2 = (
point3 [ 0 ] - point2 [ 0 ] ,
point3 [ 1 ] - point2 [ 1 ] ,
point3 [ 2 ] - point2 [ 2 ]
)
# do vector x mutiply
# vector1 x vector2
nor = [
vector1 [ 1 ] * vector2 [ 2 ] - vector1 [ 2 ] * vector2 [ 1 ] ,
vector1 [ 2 ] * vector2 [ 0 ] - vector1 [ 0 ] * vector2 [ 2 ] ,
vector1 [ 0 ] * vector2 [ 1 ] - vector1 [ 1 ] * vector2 [ 0 ]
]
# do a normalization
length = math . sqrt ( nor [ 0 ] * * 2 + nor [ 1 ] * * 2 + nor [ 2 ] * * 2 )
nor [ 0 ] / = length
nor [ 1 ] / = length
nor [ 2 ] / = length
return tuple ( nor )
2020-10-08 21:19:06 +08:00
def solve_smashed_position ( str_data , d1 , d2 ) :
sp = str_data . split ( ' ; ' )
sp_pos = sp [ 0 ] . split ( ' , ' )
sp_sync = sp [ 1 ] . split ( ' , ' )
vec = [ int ( sp_pos [ 0 ] ) , int ( sp_pos [ 1 ] ) ]
for i in range ( 2 ) :
offset = 0 if sp_sync [ i * 2 ] == ' ' else int ( sp_sync [ i * 2 ] )
if sp_sync [ i * 2 + 1 ] == ' d1 ' :
vec [ i ] + = d1 + offset
elif sp_sync [ i * 2 + 1 ] == ' d2 ' :
vec [ i ] + = d2 + offset
return tuple ( vec )
def virtual_foreach_set ( collection , field , base_num , data ) :
counter = 0
for i in data :
exec ( " a[j]. " + field + " =q " , { } , {
' a ' : collection ,
' j ' : counter + base_num ,
' q ' : i
} )
counter + = 1
2020-10-07 22:25:02 +08:00
'''
sides_struct should be a tuple and it always have 6 bool items
( use_2d_top , use_2d_right , use_2d_bottom , use_2d_left , use_3d_top , use_3d_bottom )
WARNING : this code is shared with bm import export
'''
2020-10-08 13:07:43 +08:00
def load_basic_floor ( mesh , floor_type , rotation , height_multiplier , d1 , d2 , sides_struct , extra_translate ) :
2020-10-07 22:25:02 +08:00
floor_prototype = config . floor_block_dict [ floor_type ]
# set some unit
height_unit = 5.0
if floor_prototype [ ' UnitSize ' ] == ' Small ' :
block_3dworld_unit = 2.5
block_uvworld_unit = 0.5
elif floor_prototype [ ' UnitSize ' ] == ' Large ' :
block_3dworld_unit = 5.0
block_uvworld_unit = 1.0
# got all needed faces
needCreatedFaces = [ ]
if sides_struct [ 0 ] :
needCreatedFaces . append ( face_fallback ( floor_prototype [ ' TwoDTopSide ' ] , floor_prototype [ ' TwoDTopSideExpand ' ] , height_multiplier ) )
if sides_struct [ 1 ] :
needCreatedFaces . append ( face_fallback ( floor_prototype [ ' TwoDRightSide ' ] , floor_prototype [ ' TwoDRightSideExpand ' ] , height_multiplier ) )
if sides_struct [ 2 ] :
needCreatedFaces . append ( face_fallback ( floor_prototype [ ' TwoDBottomSide ' ] , floor_prototype [ ' TwoDBottomSideExpand ' ] , height_multiplier ) )
if sides_struct [ 3 ] :
needCreatedFaces . append ( face_fallback ( floor_prototype [ ' TwoDLeftSide ' ] , floor_prototype [ ' TwoDLeftSideExpand ' ] , height_multiplier ) )
if sides_struct [ 4 ] :
needCreatedFaces . append ( floor_prototype [ ' ThreeDTopFace ' ] )
if sides_struct [ 5 ] :
needCreatedFaces . append ( floor_prototype [ ' ThreeDBottomFace ' ] )
# resolve face
# solve material first
materialDict = { }
2020-10-08 21:19:06 +08:00
allmat = mesh . materials [ : ]
counter = len ( allmat )
2020-10-07 22:25:02 +08:00
for face_define in needCreatedFaces :
for face in face_define [ ' Faces ' ] :
new_texture = face [ ' Textures ' ]
if new_texture not in materialDict . keys ( ) :
2020-10-08 21:19:06 +08:00
# try get from existed solt
pending_material = create_or_get_material ( new_texture )
if pending_material not in allmat :
# no matched. add it
mesh . materials . append ( pending_material )
materialDict [ new_texture ] = counter
counter + = 1
else :
# use existed index
materialDict [ new_texture ] = allmat . index ( pending_material )
2020-10-07 22:25:02 +08:00
# now, we can process real mesh
2020-10-08 21:19:06 +08:00
# load existed base count
global_offset_vec = len ( mesh . vertices )
global_offset_face = len ( mesh . polygons )
global_offset_facex4 = global_offset_face * 4
2020-10-07 22:25:02 +08:00
vecList = [ ]
uvList = [ ]
normalList = [ ]
faceList = [ ]
faceMatList = [ ]
for face_define in needCreatedFaces :
base_indices = len ( vecList )
for vec in face_define [ ' Vertices ' ] :
2020-10-08 13:07:43 +08:00
vecList . append ( rotate_translate_vec (
2020-10-07 22:25:02 +08:00
solve_vec_data ( vec , d1 , d2 , height_multiplier , block_3dworld_unit , height_unit ) ,
2020-10-08 13:07:43 +08:00
rotation , block_3dworld_unit , extra_translate ) )
2020-10-07 22:25:02 +08:00
for uv in face_define [ ' UVs ' ] :
uvList . append ( solve_uv_data ( uv , d1 , d2 , height_multiplier , block_uvworld_unit ) )
for face in face_define [ ' Faces ' ] :
vec_indices = (
face [ ' P1 ' ] + base_indices ,
face [ ' P2 ' ] + base_indices ,
face [ ' P3 ' ] + base_indices ,
face [ ' P4 ' ] + base_indices )
# we need calc normal and push it into list
2020-10-08 13:07:43 +08:00
four_point_normal = solve_normal_data ( vecList [ vec_indices [ 0 ] ] , vecList [ vec_indices [ 1 ] ] , vecList [ vec_indices [ 2 ] ] )
2020-10-07 22:25:02 +08:00
for i in range ( 4 ) :
normalList . append ( four_point_normal )
# push indices into list
for i in range ( 4 ) :
2020-10-08 21:19:06 +08:00
faceList . append ( vec_indices [ i ] + global_offset_vec )
2020-10-07 22:25:02 +08:00
# push material into list
faceMatList . append ( materialDict [ face [ ' Textures ' ] ] )
# push data into blender struct
mesh . vertices . add ( len ( vecList ) )
mesh . loops . add ( len ( faceMatList ) * 4 ) # 4 vec face confirm
mesh . polygons . add ( len ( faceMatList ) )
2020-10-08 21:19:06 +08:00
if mesh . uv_layers . active is None :
# if no uv, create it
mesh . uv_layers . new ( do_init = False )
mesh . create_normals_split ( )
2020-10-07 22:25:02 +08:00
2020-10-08 21:19:06 +08:00
virtual_foreach_set ( mesh . vertices , " co " , global_offset_vec , vecList )
virtual_foreach_set ( mesh . loops , " vertex_index " , global_offset_facex4 , faceList )
virtual_foreach_set ( mesh . loops , " normal " , global_offset_facex4 , normalList )
virtual_foreach_set ( mesh . uv_layers [ 0 ] . data , " uv " , global_offset_facex4 , uvList )
2020-10-07 22:25:02 +08:00
for i in range ( len ( faceMatList ) ) :
2020-10-08 21:19:06 +08:00
mesh . polygons [ i + global_offset_face ] . loop_start = i * 4 + global_offset_facex4
mesh . polygons [ i + global_offset_face ] . loop_total = 4
mesh . polygons [ i + global_offset_face ] . material_index = faceMatList [ i ]
mesh . polygons [ i + global_offset_face ] . use_smooth = True
2020-10-07 22:25:02 +08:00
2020-10-08 21:19:06 +08:00
def load_derived_floor ( mesh , floor_type , height_multiplier , d1 , d2 , sides_struct ) :
floor_prototype = config . floor_block_dict [ floor_type ]
# set some unit
if floor_prototype [ ' UnitSize ' ] == ' Small ' :
block_3dworld_unit = 2.5
elif floor_prototype [ ' UnitSize ' ] == ' Large ' :
block_3dworld_unit = 5.0
# construct face dict
sides_dict = {
' True ' : True ,
' False ' : False ,
' 2dTop ' : sides_struct [ 0 ] ,
' 2dRight ' : sides_struct [ 1 ] ,
' 2dBottom ' : sides_struct [ 2 ] ,
' 2dLeft ' : sides_struct [ 3 ] ,
' 3dTop ' : sides_struct [ 4 ] ,
' 3dBottom ' : sides_struct [ 5 ]
}
# iterate smahsed blocks
for blk in floor_prototype [ ' SmashedBlocks ' ] :
start_pos = solve_smashed_position ( blk [ ' StartPosition ' ] , d1 , d2 )
expand_pos = solve_smashed_position ( blk [ ' ExpandPosition ' ] , d1 , d2 )
sides_data = tuple ( sides_dict [ x ] for x in blk [ ' SideSync ' ] . split ( ' ; ' ) )
# call basic floor creator
load_basic_floor (
mesh ,
blk [ ' Type ' ] ,
blk [ ' Rotation ' ] ,
height_multiplier ,
expand_pos [ 0 ] ,
expand_pos [ 1 ] ,
sides_data ,
( start_pos [ 0 ] * block_3dworld_unit , start_pos [ 1 ] * block_3dworld_unit )
)