новая структура проекта

This commit is contained in:
2026-04-20 21:04:25 +04:00
parent 1fe2a72ef1
commit 1a56b22e38
1932 changed files with 1886 additions and 22779 deletions
@@ -0,0 +1,297 @@
import bpy
import gpu
import mathutils
from typing import Union
from bgl import *
from ...wbs_kernel.render import CWMODrawingBatch
from ...wbs_kernel.render import OpenGLUtils
from .shaders import WMOShaderPermutations, EGxBLend
from .drawing_material import WMODrawingMaterial
from ..drawing_elements import ElementTypes
from ..utils import render_debug
from ..bgl_ext import glCheckError
class WMODrawingBatch:
c_batch: CWMODrawingBatch
# control
mesh_type: int = ElementTypes.WmoMesh
shader: gpu.types.GPUShader
bl_batch_vert_shader_id: int
bl_batch_frag_shader_id: int
# uniform data
draw_material: Union[CWMODrawingBatch, None]
def __init__(self
, c_batch: 'CWMODrawingBatch'
, draw_obj: 'WMODrawingObject'
, context: bpy.types.Context):
self.c_batch = c_batch
self.context = context
self.draw_obj = draw_obj
self.tag_free = False
self.mat_id = self.c_batch.get_mat_id()
try:
self.draw_material = self.draw_obj.draw_mgr.draw_materials.get(
self.draw_obj.bl_obj.data.materials[self.mat_id].name)
except IndexError:
self.draw_material = None
self.create_vao()
self.draw_obj.draw_mgr.draw_elements.add_batch(self)
render_debug('Instantiated drawing batch for object \"{}\"'.format(draw_obj.bl_obj.name))
@property
def is_transparent(self) -> bool:
return False
@property
def priority_plane(self) -> int:
return 0
@property
def layer(self) -> int:
return 0
@property
def is_skybox(self) -> bool:
return False
@property
def bb_center(self) -> mathutils.Vector:
return mathutils.Vector(self.c_batch.bb_center)
@property
def sort_radius(self) -> float:
return self.c_batch.sort_radius
@property
def sort_distance(self):
perspective_mat = self.draw_obj.draw_mgr.region_3d.perspective_matrix
bb_center = self.draw_obj.bl_obj.matrix_world @ self.bb_center
value = (perspective_mat.to_translation() - bb_center).length
if self.draw_material.is_inverted or self.draw_material.is_transformed:
result_point = bb_center * (1.0 / value) if value > 0.00000023841858 else bb_center
sort_dist = perspective_mat.to_translation().length * self.sort_radius
result_point *= sort_dist
value = (bb_center - result_point).length \
if self.draw_material.is_inverted else (bb_center + result_point).length
return value
def create_vao(self):
self.shader = self.determine_valid_shader()
self.c_batch.set_program(self.shader.program)
self.c_batch.create_vao()
glCheckError('Create VAO')
def ensure_context(self):
mat_test = self.draw_material.bl_material
if not mat_test:
self.tag_free = True
return self.tag_free
def _set_active_textures(self):
gl_texture_slots = (
GL_TEXTURE0,
GL_TEXTURE1,
GL_TEXTURE2,
GL_TEXTURE3
)
for i, gl_slot in enumerate(gl_texture_slots):
bind_code = self.draw_material.get_bindcode(i)
if bind_code:
glActiveTexture(gl_slot)
glBindTexture(GL_TEXTURE_2D, bind_code)
def determine_valid_shader(self) -> gpu.types.GPUShader:
shaders = WMOShaderPermutations()
if self.draw_material:
shader_rec = WMOShaderPermutations.get_shader_combo(int(self.draw_material.bl_material.wow_wmo_material.shader))
self.bl_batch_vert_shader_id = shader_rec.vertex_shader
self.bl_batch_frag_shader_id = shader_rec.pixel_shader
return shaders.get_shader_by_id(self.bl_batch_vert_shader_id,
self.bl_batch_frag_shader_id)
else:
return shaders.default_shader
def draw(self):
render_debug('Drawing batch for object \"{}\"'.format(self.draw_obj.bl_obj.name))
bl_obj = self.draw_obj.bl_obj
if not bl_obj.visible_get():
return
if self.tag_free:
return
if self.draw_material:
self.draw_wmo_batch()
else:
self.draw_fallback()
def draw_fallback(self):
glCheckError('drawfallback pre')
self.shader = self.determine_valid_shader()
self.shader.bind()
self.c_batch.set_program(self.shader.program)
self.shader.uniform_float('uViewProjectionMatrix', self.draw_obj.draw_mgr.region_3d.perspective_matrix)
self.shader.uniform_float('uPlacementMatrix', self.draw_obj.bl_obj.matrix_world)
self.shader.uniform_float('uSunDirAndFogStart', self.draw_obj.draw_mgr.sun_dir_and_fog_start)
self.shader.uniform_float('uSunColorAndFogEnd', self.draw_obj.draw_mgr.sun_color_and_fog_end)
self.shader.uniform_float('uAmbientLight', self.draw_obj.draw_mgr.ambient_light)
self.shader.uniform_float('uFogColorAndAlphaTest', (*self.draw_obj.draw_mgr.fog_color, 1.0 / 255.0))
self.shader.uniform_int('UnFogged_IsAffectedByLight_LightCount', (False, True, 0))
glEnable(GL_DEPTH_TEST)
self.c_batch.draw()
glDisable(GL_DEPTH_TEST)
gpu.shader.unbind()
glCheckError('draw fallback post')
def draw_wmo_batch(self):
glCheckError('draw')
#render_debug('Drawing batch for object \"{}\"'.format(self.draw_obj.bl_obj.name))
color_name = self.draw_material.bl_material.wow_m2_material.color
transparency_name = self.draw_material.bl_material.wow_m2_material.transparency
color = self.context.scene.wow_m2_colors[color_name].color if color_name else (1.0, 1.0, 1.0, 1.0)
if transparency_name:
transparency_rec = self.context.scene.wow_m2_transparency.get(transparency_name)
transparency = transparency_rec.value if transparency_rec else 1.0
else:
transparency = 1.0
combined_color = (*color[:3], color[3] * transparency)
u_alpha_test = 128.0 / 255.0 * combined_color[3] \
if self.draw_material.blend_mode.index == EGxBLend.AlphaKey.index else 1.0 / 255.0 # Maybe move this to shader logic?
self._set_active_textures()
self.shader = self.determine_valid_shader()
self.shader.bind()
glCheckError('Pre-link program')
self.c_batch.set_program(self.shader.program)
glCheckError('Post-link program')
# draw
if self.draw_material.depth_culling:
glEnable(GL_DEPTH_TEST)
glDepthMask(GL_TRUE if self.draw_material.depth_write else GL_FALSE)
if self.draw_material.backface_culling:
glEnable(GL_CULL_FACE)
if self.draw_material.blend_mode.blending_enabled:
glEnable(GL_BLEND)
if self.is_skybox:
glDepthRange(0.998, 1.0)
glBlendFunc(self.draw_material.blend_mode.src_color, self.draw_material.blend_mode.dest_color)
# OpenGLUtils.glBlendFuncSeparate(self.draw_material.blend_mode.src_color,
# self.draw_material.blend_mode.dest_color,
# self.draw_material.blend_mode.src_alpha,
# self.draw_material.blend_mode.dest_alpha)
self.shader.uniform_float('uViewProjectionMatrix', self.draw_obj.draw_mgr.region_3d.perspective_matrix)
self.shader.uniform_float('uPlacementMatrix', self.draw_obj.bl_obj.matrix_world)
self.shader.uniform_float('uSunDirAndFogStart', self.draw_obj.draw_mgr.sun_dir_and_fog_start)
self.shader.uniform_float('uSunColorAndFogEnd', self.draw_obj.draw_mgr.sun_color_and_fog_end)
self.shader.uniform_float('uAmbientLight', self.draw_obj.draw_mgr.ambient_light)
self.shader.uniform_float('uFogColorAndAlphaTest', (*self.draw_obj.draw_mgr.fog_color, u_alpha_test))
self.shader.uniform_int('UnFogged_IsAffectedByLight_LightCount', (self.draw_material.is_unfogged,
not self.draw_material.is_unlit, 0))
try:
self.shader.uniform_int('uTexture', 0)
except ValueError:
pass
try:
self.shader.uniform_int('uTexture2', 1)
except ValueError:
pass
try:
self.shader.uniform_int('uTexture3', 2)
except ValueError:
pass
try:
self.shader.uniform_int('uTexture4', 3)
except ValueError:
pass
self.shader.uniform_float('color_Transparency', combined_color)
self.c_batch.draw()
if self.is_skybox:
glDepthRange(0, 0.996)
if self.draw_material.blend_mode.blending_enabled:
glDisable(GL_BLEND)
if self.draw_material.backface_culling:
glDisable(GL_CULL_FACE)
glDepthMask(GL_FALSE if self.draw_material.depth_write else GL_TRUE)
if self.draw_material.depth_culling:
glDisable(GL_DEPTH_TEST)
gpu.shader.unbind()
glCheckError('draw end')
def free(self):
if self.tag_free:
return
self.tag_free = True
self.draw_obj.draw_mgr.draw_elements.remove_batch(self)
@@ -0,0 +1,89 @@
import bpy
from .shaders import EGxBlendRecord, WMOBlendingModeToEGxBlend
class WMODrawingMaterial:
__slots__ = (
'draw_mgr',
'bl_material_name',
'blend_mode',
'backface_culling',
'is_unlit',
'is_unfogged',
'is_exterior_lit',
'night_glow',
'is_window',
'clamp_s',
'clamp_t',
)
blend_mode: EGxBlendRecord
backface_culling: bool
is_unlit: bool
is_unfogged: bool
is_exterior_lit: bool
night_glow: bool
is_window: bool
clamp_s: bool
clamp_t: bool
def __init__(self, material: bpy.types.Material):
self.bl_material_name = material.name
self.update_uniform_data()
@property
def bl_material(self):
try:
return bpy.data.materials[self.bl_material_name]
except KeyError:
return None
def get_texture(self, tex_index: int):
mat = self.bl_material
if mat:
return getattr(mat.wow_wmo_material, 'diff_texture_{}'.format(tex_index + 1))
@property
def texture_count(self):
mat = self.bl_material
counter = 0
if mat:
for i in range(2):
tex = getattr(mat.wow_wmo_material, 'diff_texture_{}'.format(i + 1))
if tex:
counter += 1
return counter
def update_uniform_data(self):
bl_material = self.bl_material
if not bl_material:
return
self.blend_mode = WMOBlendingModeToEGxBlend[int(self.bl_material.wow_wmo_material.blending_mode)]
self.backface_culling = '4' not in bl_material.wow_wmo_material.render_flags
self.is_unlit = '1' in bl_material.wow_wmo_material.render_flags
self.is_unfogged = '2' in bl_material.wow_wmo_material.render_flags
self.is_exterior_lit = '8' in bl_material.wow_wmo_material.render_flags
self.night_glow = '16' in bl_material.wow_wmo_material.render_flags
self.is_window = '32' in bl_material.wow_wmo_material.render_flags
self.clamp_s = '64' in bl_material.wow_wmo_material.render_flags
self.clamp_t = '128' in bl_material.wow_wmo_material.render_flags
def get_bindcode(self, tex_index: int) -> int:
texture = self.get_texture(tex_index)
if texture:
if not texture.bindcode:
texture.gl_load()
return texture.bindcode
return 0
@@ -0,0 +1,108 @@
import traceback
import bpy
import numpy as np
from mathutils import Matrix
from typing import List
from .drawing_batch import M2DrawingBatch
from ..utils import render_debug
from ...wbs_kernel.render import CM2DrawingMesh, CM2DrawingBatch
from ..bgl_ext import glCheckError
class M2DrawingObject:
batches: List[CM2DrawingBatch]
def __init__(self
, bl_obj: bpy.types.Object
, drawing_mgr: 'M2DrawingManager'
, context: bpy.types.Context
, is_skybox: bool = False):
self.context = context
self.draw_mgr = drawing_mgr
self.bl_obj_name = bl_obj.name
self.is_skybox = is_skybox
self.is_dirty = True
self.is_batching_valid = False
self.mesh_ptr = bl_obj.data.as_pointer()
self.c_mesh = CM2DrawingMesh(self.mesh_ptr)
self.batches = []
bl_obj.data.calc_loop_triangles()
render_debug('Initialized drawing object \"{}\"'.format(self.bl_obj_name))
self.update_geometry()
def update_geometry(self, bl_obj: bpy.types.Object = None):
if bl_obj:
self.c_mesh.update_mesh_pointer(bl_obj.data.as_pointer())
bl_obj.data.calc_loop_triangles()
self.is_batching_valid = self.c_mesh.update_geometry(not(self.context.screen.is_animation_playing or self.bl_obj.mode != 'OBJECT'))
self.is_dirty = True
def update_geometry_opengl(self, bl_obj: bpy.types.Object = None):
self.c_mesh.update_buffers()
if not self.is_batching_valid:
for batch in self.batches:
batch.free()
self.batches = \
[M2DrawingBatch(c_batch, self, self.context) for c_batch in self.c_mesh.get_drawing_batches()]
self.is_dirty = False
@property
def bl_obj(self):
try:
return bpy.data.objects[self.bl_obj_name]
except KeyError:
self.free()
def free(self):
for batch in self.batches:
batch.free()
del self.draw_mgr.m2_objects[self.bl_obj_name]
render_debug('Freed drawing object \"{}\"'.format(self.bl_obj_name))
'''
def update_bone_matrices(self):
rig = self.bl_rig
for i, pbone in enumerate(rig.pose.bones):
self.bone_matrices[i] = [j[i] for i in range(4) for j in pbone.matrix_channel]
def create_batches_from_armature(self, rig: bpy.types.Object):
for obj in filter(lambda x: x.type == 'MESH', rig.children):
# Limit bone influences to 4. TODO: rework to be non-destructive!
"""
if obj.vertex_groups:
active_obj = bpy.context.view_layer.objects.active
bpy.context.view_layer.objects.active = obj
bpy.ops.object.vertex_group_limit_total()
bpy.context.view_layer.objects.active = active_obj
"""
self.create_batch_from_object(obj)
def create_batch_from_object(self, obj: bpy.types.Object):
self.batches[obj.name] = M2DrawingBatch(obj, self, self.context)
'''
@@ -0,0 +1,173 @@
import os
import gpu
from enum import IntEnum
from ctypes import c_uint, c_uint8
from collections import namedtuple
from typing import Dict, Tuple
from ...utils.misc import singleton, Sequence
from ..shaders import ShaderPermutationsManager
from bgl import *
class WMOPixelShader(IntEnum):
NoShader = -1,
MapObjDiffuse = 0,
MapObjSpecular = 1,
MapObjMetal = 2,
MapObjEnv = 3,
MapObjOpaque = 4,
MapObjEnvMetal = 5,
MapObjTwoLayerDiffuse = 6, # MapObjComposite
MapObjTwoLayerEnvMetal = 7,
MapObjTwoLayerTerrain = 8,
MapObjDiffuseEmissive = 9,
MapObjMaskedEnvMetal = 10,
MapObjEnvMetalEmissive = 11,
MapObjTwoLayerDiffuseOpaque = 12,
MapObjTwoLayerDiffuseEmissive = 13,
MapObjAdditiveMaskedEnvMetal = 14,
MapObjTwoLayerDiffuseMod2x = 15,
MapObjTwoLayerDiffuseMod2xNA = 16,
MapObjTwoLayerDiffuseAlpha = 17,
MapObjLod = 18,
MapObjParallax = 19
class WMOVertexShader(IntEnum):
NoShader = -1,
MapObjDiffuse_T1 = 0,
MapObjDiffuse_T1_Refl = 1,
MapObjDiffuse_T1_Env_T2 = 2,
MapObjSpecular_T1 = 3,
MapObjDiffuse_Comp = 4,
MapObjDiffuse_Comp_Refl = 5,
MapObjDiffuse_Comp_Terrain = 6,
MapObjDiffuse_CompAlpha = 7,
MapObjParallax = 8,
EGxBlendRecord = namedtuple('EGxBlendRecord',
['blending_enabled', 'src_color', 'dest_color', 'src_alpha', 'dest_alpha', 'index'])
class EGxBLend(metaclass=Sequence):
Opaque = EGxBlendRecord(False, GL_ONE, GL_ZERO, GL_ONE, GL_ZERO, 0)
AlphaKey = EGxBlendRecord(False, GL_ONE, GL_ZERO, GL_ONE, GL_ZERO, 1)
Alpha = EGxBlendRecord(True, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA, 2)
Add = EGxBlendRecord(True, GL_SRC_ALPHA, GL_ONE, GL_ZERO, GL_ONE, 3)
Mod = EGxBlendRecord(True, GL_DST_COLOR, GL_ZERO, GL_DST_ALPHA, GL_ZERO, 4)
Mod2x = EGxBlendRecord(True, GL_DST_COLOR, GL_SRC_COLOR, GL_DST_ALPHA, GL_SRC_ALPHA, 5)
ModAdd = EGxBlendRecord(True, GL_DST_COLOR, GL_ONE, GL_DST_ALPHA, GL_ONE, 6)
InvSrcAlphaAdd = EGxBlendRecord(True, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, 7)
InvSrcAlphaOpaque = EGxBlendRecord(True, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, 8)
SrcAlphaOpaque = EGxBlendRecord(True, GL_SRC_ALPHA, GL_ZERO, GL_SRC_ALPHA, GL_ZERO, 9)
NoAlphaAdd = EGxBlendRecord(True, GL_ONE, GL_ONE, GL_ZERO, GL_ONE, 10)
ConstantAlpha = EGxBlendRecord(True, GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA, GL_CONSTANT_ALPHA,
GL_ONE_MINUS_CONSTANT_ALPHA, 11)
Screen = EGxBlendRecord(True, GL_ONE_MINUS_DST_COLOR, GL_ONE, GL_ONE, GL_ZERO, 12)
BlendAdd = EGxBlendRecord(True, GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA, 13)
class WMOBlendingModeToEGxBlend(metaclass=Sequence):
Blend_Opaque = EGxBLend.Opaque
Blend_AlphaKey = EGxBLend.AlphaKey
Blend_Alpha = EGxBLend.Alpha
Blend_NoAlphaAdd = EGxBLend.NoAlphaAdd
Blend_Add = EGxBLend.Add
Blend_Mod = EGxBLend.Mod
Blend_Mod2x = EGxBLend.Mod2x
Blend_BlendAdd = EGxBLend.BlendAdd
WMOShaderTableRecord = namedtuple('WMOShaderTableRecord', ['pixel_shader', 'vertex_shader'])
class WMOShaderTable(metaclass=Sequence):
MapObjDiffuse = WMOShaderTableRecord(WMOPixelShader.MapObjDiffuse, WMOVertexShader.MapObjDiffuse_T1)
MapObjSpecular = WMOShaderTableRecord(WMOPixelShader.MapObjSpecular, WMOVertexShader.MapObjSpecular_T1)
MapObjMetal = WMOShaderTableRecord(WMOPixelShader.MapObjMetal, WMOVertexShader.MapObjSpecular_T1)
MapObjEnv = WMOShaderTableRecord(WMOPixelShader.MapObjEnv, WMOVertexShader.MapObjDiffuse_T1_Refl)
MapObjOpaque = WMOShaderTableRecord(WMOPixelShader.MapObjOpaque, WMOVertexShader.MapObjDiffuse_T1)
MapObjEnvMetal = WMOShaderTableRecord(WMOPixelShader.MapObjEnvMetal, WMOVertexShader.MapObjDiffuse_T1_Refl)
MapObjTwoLayerDiffuse = WMOShaderTableRecord(WMOPixelShader.MapObjTwoLayerDiffuse,
WMOVertexShader.MapObjDiffuse_Comp)
MapObjTwoLayerEnvMetal = WMOShaderTableRecord(WMOPixelShader.MapObjTwoLayerEnvMetal,
WMOVertexShader.MapObjDiffuse_T1)
TwoLayerTerrain = WMOShaderTableRecord(WMOPixelShader.MapObjTwoLayerTerrain,
WMOVertexShader.MapObjDiffuse_Comp_Terrain)
MapObjDiffuseEmissive = WMOShaderTableRecord(WMOPixelShader.MapObjDiffuseEmissive,
WMOVertexShader.MapObjDiffuse_Comp)
waterWindow = WMOShaderTableRecord(WMOPixelShader.NoShader,
WMOVertexShader.NoShader)
MapObjMaskedEnvMetal = WMOShaderTableRecord(WMOPixelShader.MapObjMaskedEnvMetal,
WMOVertexShader.MapObjDiffuse_T1_Env_T2)
MapObjEnvMetalEmissive = WMOShaderTableRecord(WMOPixelShader.MapObjEnvMetalEmissive,
WMOVertexShader.MapObjDiffuse_T1_Env_T2)
TwoLayerDiffuseOpaque = WMOShaderTableRecord(WMOPixelShader.MapObjTwoLayerDiffuseOpaque,
WMOVertexShader.MapObjDiffuse_Comp)
submarineWindow = WMOShaderTableRecord(WMOPixelShader.NoShader, WMOVertexShader.NoShader)
TwoLayerDiffuseEmissive = WMOShaderTableRecord(WMOPixelShader.MapObjTwoLayerDiffuseEmissive,
WMOVertexShader.MapObjDiffuse_Comp)
MapObjDiffuseTerrain = WMOShaderTableRecord(WMOPixelShader.MapObjDiffuse,
WMOVertexShader.MapObjDiffuse_T1)
MapObjAdditiveMaskedEnvMetal = WMOShaderTableRecord(WMOPixelShader.MapObjAdditiveMaskedEnvMetal,
WMOVertexShader.MapObjDiffuse_T1_Env_T2)
MapObjTwoLayerDiffuseMod2x = WMOShaderTableRecord(WMOPixelShader.MapObjTwoLayerDiffuseMod2x,
WMOVertexShader.MapObjDiffuse_CompAlpha)
MapObjTwoLayerDiffuseMod2xNA = WMOShaderTableRecord(WMOPixelShader.MapObjTwoLayerDiffuseMod2xNA,
WMOVertexShader.MapObjDiffuse_Comp)
MapObjTwoLayerDiffuseAlpha = WMOShaderTableRecord(WMOPixelShader.MapObjTwoLayerDiffuseAlpha,
WMOVertexShader.MapObjDiffuse_CompAlpha)
MapObjLod = WMOShaderTableRecord(WMOPixelShader.MapObjLod,
WMOVertexShader.MapObjDiffuse_T1)
MapObjParallax = WMOShaderTableRecord(WMOPixelShader.MapObjParallax,
WMOVertexShader.MapObjParallax)
@singleton
class WMOShaderPermutations(ShaderPermutationsManager):
shader_source_path = 'wmo_shader'
@staticmethod
def get_shader_combo_index(vertex_shader_id: int, pixel_shader_id: int):
for record in WMOShaderTable:
if record.value.vertex_shader == vertex_shader_id and record.value.pixel_shader == pixel_shader_id:
return record.index
def get_shader_combo(self, id: int):
# TODO: ugly
count = 0
for i, record in enumerate(WMOShaderTable):
if i == count:
return record
count += 1