Initial commit
This commit is contained in:
parent
fd516d9e0e
commit
995bf859b5
|
@ -0,0 +1,2 @@
|
|||
/build*/
|
||||
__pycache__/
|
|
@ -0,0 +1,13 @@
|
|||
cmake_minimum_required(VERSION 3.11)
|
||||
|
||||
set(LIB_NAME symmetrize_texture)
|
||||
|
||||
project(${LIB_NAME})
|
||||
|
||||
add_library(${LIB_NAME} SHARED
|
||||
src/symtex_processor.cpp
|
||||
src/math_util.cpp
|
||||
|
||||
src/symtex_processor.h
|
||||
src/math_util.h
|
||||
)
|
|
@ -0,0 +1,88 @@
|
|||
'''
|
||||
Copyright (C) 2021 - 2023 Akaneyu
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
bl_info = {
|
||||
"name": "Symmetrize Texture",
|
||||
"author": "akaneyu",
|
||||
"version": (1, 1, 3),
|
||||
"blender": (2, 93, 0),
|
||||
"location": "View3D",
|
||||
"warning": "",
|
||||
"description": "",
|
||||
"wiki_url": "",
|
||||
"tracker_url": "",
|
||||
"category": "3D View"}
|
||||
|
||||
if "bpy" in locals():
|
||||
import importlib
|
||||
importlib.reload(app)
|
||||
importlib.reload(operators)
|
||||
importlib.reload(ui)
|
||||
importlib.reload(ui_renderer)
|
||||
importlib.reload(utils)
|
||||
|
||||
import bpy
|
||||
from . import app
|
||||
from . import operators
|
||||
from . import ui
|
||||
|
||||
classes = [
|
||||
app.SYMMETRIZE_TEXTURE_PropertyGroup,
|
||||
operators.SYMMETRIZE_TEXTURE_OT_use_3d_brush,
|
||||
operators.SYMMETRIZE_TEXTURE_OT_mirrored_copy,
|
||||
operators.SYMMETRIZE_TEXTURE_OT_use_2d_brush,
|
||||
operators.SYMMETRIZE_TEXTURE_OT_save_image,
|
||||
ui.SYMMETRIZE_TEXTURE_MT_menu_3d,
|
||||
ui.SYMMETRIZE_TEXTURE_MT_menu_2d,
|
||||
ui.SYMMETRIZE_TEXTURE_PT_panel_3d,
|
||||
ui.SYMMETRIZE_TEXTURE_PT_panel_2d
|
||||
]
|
||||
|
||||
def register():
|
||||
app.load_icons()
|
||||
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
bpy.types.VIEW3D_MT_object.append(ui.menu_func_3d)
|
||||
bpy.types.IMAGE_MT_image.append(ui.menu_func_2d)
|
||||
|
||||
wm = bpy.types.WindowManager
|
||||
|
||||
wm.symmetrizetexture_properties = \
|
||||
bpy.props.PointerProperty(type=app.SYMMETRIZE_TEXTURE_PropertyGroup)
|
||||
|
||||
app.SYMMETRIZE_TEXTURE_PropertyGroup.image_mirror_axis = \
|
||||
bpy.props.EnumProperty(items=(
|
||||
('x_axis', 'X', 'X Axis', ui.get_icon_id('mirror_x'), 0),
|
||||
('y_axis', 'Y', 'Y Axis', ui.get_icon_id('mirror_y'), 1)))
|
||||
|
||||
def unregister():
|
||||
app.dispose_icons()
|
||||
|
||||
for cls in classes:
|
||||
bpy.utils.unregister_class(cls)
|
||||
|
||||
bpy.types.VIEW3D_MT_object.remove(ui.menu_func_3d)
|
||||
bpy.types.IMAGE_MT_image.remove(ui.menu_func_2d)
|
||||
|
||||
wm = bpy.types.WindowManager
|
||||
|
||||
del wm.symmetrizetexture_properties
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -0,0 +1,166 @@
|
|||
'''
|
||||
Copyright (C) 2021 - 2023 Akaneyu
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
import sys
|
||||
import os
|
||||
import ctypes
|
||||
import math
|
||||
import bpy
|
||||
import bpy.utils.previews
|
||||
import blf
|
||||
import numpy as np
|
||||
from . ui_renderer import UIRenderer as UIRenderer
|
||||
from . import utils
|
||||
|
||||
class Session:
|
||||
def __init__(self):
|
||||
self.icons = None
|
||||
self.ui_renderer = None
|
||||
self.draw_handler = None
|
||||
self.previous_object = None
|
||||
self.brush_position = None
|
||||
self.brush_size = 50.0
|
||||
self.selecting_direction = False
|
||||
self.brush_active = False
|
||||
self.resizing_brush = False
|
||||
|
||||
def get_session():
|
||||
global session
|
||||
|
||||
return session
|
||||
|
||||
def draw_handler():
|
||||
global session
|
||||
|
||||
context = bpy.context
|
||||
|
||||
wm = context.window_manager
|
||||
props = wm.symmetrizetexture_properties
|
||||
mirror_axis = props.image_mirror_axis
|
||||
|
||||
info_text = None
|
||||
|
||||
if not session.ui_renderer:
|
||||
session.ui_renderer = UIRenderer()
|
||||
|
||||
# direction setup
|
||||
if session.selecting_direction:
|
||||
if mirror_axis == 'x_axis':
|
||||
border_pos1 = context.region.view2d.view_to_region(0.5, 0, clip=False)
|
||||
border_pos2 = context.region.view2d.view_to_region(0.5, 1.0, clip=False)
|
||||
else:
|
||||
border_pos1 = context.region.view2d.view_to_region(0, 0.5, clip=False)
|
||||
border_pos2 = context.region.view2d.view_to_region(1.0, 0.5, clip=False)
|
||||
|
||||
session.ui_renderer.render_border(border_pos1, border_pos2)
|
||||
|
||||
center = context.region.view2d.view_to_region(0.5, 0.5, clip=False)
|
||||
|
||||
if mirror_axis == 'x_axis':
|
||||
arrow_angle = 0 if session.direction > 0 else np.pi
|
||||
else:
|
||||
arrow_angle = np.pi / 2.0 if session.direction > 0 else np.pi * 1.5
|
||||
|
||||
session.ui_renderer.render_arrow(center, arrow_angle)
|
||||
|
||||
info_text = "LMB: Perform\n" \
|
||||
+ "RMB: Cancel"
|
||||
|
||||
# brush
|
||||
if session.brush_active and session.brush_position:
|
||||
session.ui_renderer.render_brush_frame(session.brush_position, session.brush_size)
|
||||
|
||||
info_text = "LMB: Perform\n" \
|
||||
+ "RMB: Finish\n" \
|
||||
+ "F: Change brush size"
|
||||
|
||||
area_height = context.area.height
|
||||
|
||||
# info text
|
||||
if info_text:
|
||||
blf.enable(0, blf.WORD_WRAP)
|
||||
blf.word_wrap(0, 200)
|
||||
blf.color(0, 1.0, 1.0, 1.0, 1.0)
|
||||
|
||||
if bpy.context.area.type == 'VIEW_3D':
|
||||
blf.position(0, 85, area_height - 150, 0)
|
||||
else:
|
||||
blf.position(0, 85, area_height - 70, 0)
|
||||
|
||||
blf.size(0, 14, 72)
|
||||
blf.draw(0, info_text)
|
||||
|
||||
blf.disable(0, blf.WORD_WRAP)
|
||||
|
||||
def load_icons():
|
||||
global session
|
||||
|
||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
icons = bpy.utils.previews.new()
|
||||
|
||||
icons_dir = os.path.join(script_dir, "icons")
|
||||
for file_name in os.listdir(icons_dir):
|
||||
icon_name = os.path.splitext(file_name)[0]
|
||||
icons.load(icon_name, os.path.join(icons_dir, file_name), 'IMAGE')
|
||||
|
||||
session.icons = icons
|
||||
|
||||
def dispose_icons():
|
||||
global session
|
||||
|
||||
bpy.utils.previews.remove(session.icons)
|
||||
|
||||
def load_native_library():
|
||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
if os.name == 'nt':
|
||||
lib_file_name = 'symmetrize_texture.dll'
|
||||
else:
|
||||
lib_file_name = 'libsymmetrize_texture.so'
|
||||
|
||||
lib = ctypes.CDLL(os.path.join(script_dir, lib_file_name))
|
||||
|
||||
return lib
|
||||
|
||||
def unload_native_library(lib):
|
||||
if os.name == 'nt':
|
||||
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
|
||||
kernel32.FreeLibrary.argtypes = [ctypes.c_void_p]
|
||||
kernel32.FreeLibrary(lib._handle)
|
||||
else:
|
||||
stdlib = ctypes.CDLL("")
|
||||
stdlib.dlclose.argtypes = [ctypes.c_void_p]
|
||||
stdlib.dlclose(lib._handle)
|
||||
|
||||
def get_image_previews(self, context):
|
||||
image_previews = []
|
||||
for i, img in enumerate(bpy.data.images):
|
||||
image_previews.append((img.name, img.name, img.name, bpy.types.UILayout.icon(img), i))
|
||||
|
||||
return image_previews
|
||||
|
||||
class SYMMETRIZE_TEXTURE_PropertyGroup(bpy.types.PropertyGroup):
|
||||
image_preview: bpy.props.EnumProperty(items=get_image_previews, options={'LIBRARY_EDITABLE'})
|
||||
|
||||
# created in the register(): image_mirror_axis
|
||||
|
||||
brush_strength: bpy.props.FloatProperty(name='Strength', default=1.0, min=0, max=1.0, precision=3)
|
||||
brush_falloff: bpy.props.EnumProperty(items=(
|
||||
('smooth', 'Smooth', 'Smooth', 'SMOOTHCURVE', 0),
|
||||
('constant', 'Constant', 'Constant', 'NOCURVE', 1)))
|
||||
|
||||
session = Session()
|
Binary file not shown.
After Width: | Height: | Size: 793 B |
Binary file not shown.
After Width: | Height: | Size: 581 B |
Binary file not shown.
After Width: | Height: | Size: 588 B |
Binary file not shown.
After Width: | Height: | Size: 926 B |
|
@ -0,0 +1,533 @@
|
|||
'''
|
||||
Copyright (C) 2021 - 2023 Akaneyu
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHAmathNTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
import ctypes
|
||||
import math
|
||||
import bpy
|
||||
import mathutils
|
||||
import numpy as np
|
||||
from . import app
|
||||
from . import utils
|
||||
|
||||
class SYMMETRIZE_TEXTURE_OT_use_3d_brush(bpy.types.Operator):
|
||||
"""Symmetrize the texture by using 3D brush"""
|
||||
bl_idname = "symmetrize_texture.use_3d_brush"
|
||||
bl_label = "Use 3D Brush"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def __init__(self):
|
||||
self.native_lib = None
|
||||
self.lmb = False
|
||||
self.image = None
|
||||
self.image_pixels = None
|
||||
self.eval_object = None
|
||||
self.eval_mesh = None
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.mode == 'OBJECT'
|
||||
|
||||
def modal(self, context, event):
|
||||
session = app.get_session()
|
||||
|
||||
context.area.tag_redraw()
|
||||
|
||||
# for updating the 3D view
|
||||
context.tool_settings.image_paint.canvas = context.tool_settings.image_paint.canvas
|
||||
|
||||
region_pos = [event.mouse_region_x, event.mouse_region_y]
|
||||
|
||||
if session.resizing_brush:
|
||||
session.brush_position = self.resize_origin
|
||||
else:
|
||||
session.brush_position = region_pos
|
||||
|
||||
if event.type == 'MOUSEMOVE':
|
||||
if session.resizing_brush:
|
||||
session.brush_size = max(self.initial_brush_size
|
||||
+ region_pos[0] - self.resize_origin[0], 1.0)
|
||||
else:
|
||||
if self.lmb:
|
||||
self.process(context)
|
||||
elif event.type == 'LEFTMOUSE':
|
||||
if event.value == 'PRESS':
|
||||
self.lmb = True
|
||||
|
||||
if session.resizing_brush:
|
||||
session.resizing_brush = False
|
||||
else:
|
||||
self.process(context)
|
||||
elif event.value == 'RELEASE':
|
||||
self.lmb = False
|
||||
|
||||
elif event.type in ['F']:
|
||||
if not session.resizing_brush:
|
||||
self.resize_origin = region_pos
|
||||
self.initial_brush_size = session.brush_size
|
||||
|
||||
session.resizing_brush = True
|
||||
elif event.type in ['RIGHTMOUSE', 'ESC', 'RET']:
|
||||
if event.value == 'PRESS':
|
||||
if session.resizing_brush:
|
||||
session.brush_size = self.initial_brush_size
|
||||
|
||||
session.resizing_brush = False
|
||||
else:
|
||||
self.eval_object.to_mesh_clear()
|
||||
|
||||
session.brush_position = None
|
||||
session.brush_active = False
|
||||
session.resizing_brush = False
|
||||
|
||||
bpy.types.SpaceView3D.draw_handler_remove(session.draw_handler, 'WINDOW')
|
||||
|
||||
self.native_lib.SYMTEX_free()
|
||||
|
||||
app.unload_native_library(self.native_lib)
|
||||
|
||||
return {'CANCELLED'}
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
session = app.get_session()
|
||||
|
||||
wm = context.window_manager
|
||||
props = wm.symmetrizetexture_properties
|
||||
|
||||
if context.area.type != 'VIEW_3D':
|
||||
return {'CANCELLED'}
|
||||
|
||||
self.native_lib = app.load_native_library()
|
||||
|
||||
self.native_lib.SYMTEX_init(0) # 3D processor
|
||||
|
||||
session.brush_position = None
|
||||
session.brush_active = True
|
||||
|
||||
obj = context.object
|
||||
if not obj or obj.type != 'MESH':
|
||||
return {'CANCELLED'}
|
||||
|
||||
img = bpy.data.images.get(props.image_preview)
|
||||
if not img or img.use_view_as_render:
|
||||
return {'CANCELLED'}
|
||||
|
||||
self.image = img
|
||||
|
||||
depsgraph = context.evaluated_depsgraph_get()
|
||||
|
||||
self.eval_object = obj.evaluated_get(depsgraph)
|
||||
self.eval_mesh = self.eval_object.to_mesh()
|
||||
|
||||
obj_mat = obj.matrix_world
|
||||
obj_mat_inv = obj_mat.inverted()
|
||||
|
||||
region_3d = context.area.spaces.active.region_3d
|
||||
|
||||
self.native_lib.SYMTEX_setOrthogonal(int(region_3d.view_perspective == 'ORTHO'))
|
||||
|
||||
pers_mat = region_3d.perspective_matrix
|
||||
pers_mat = pers_mat @ obj_mat
|
||||
|
||||
self.native_lib.SYMTEX_setPerspectiveMatrix((ctypes.c_float * 16)
|
||||
(*[x for row in pers_mat for x in row]))
|
||||
|
||||
self.native_lib.SYMTEX_setRegionSize(context.region.width, context.region.height)
|
||||
|
||||
view_mat_inv = region_3d.view_matrix.inverted()
|
||||
|
||||
view_pos = obj_mat_inv.to_3x3() @ view_mat_inv.col[3].to_3d() \
|
||||
+ obj_mat_inv.col[3].to_3d()
|
||||
|
||||
view_dir = obj_mat_inv.to_3x3() @ view_mat_inv.to_3x3() @ mathutils.Vector((0, 0, 1.0))
|
||||
view_dir.normalize()
|
||||
|
||||
self.native_lib.SYMTEX_setViewPosition((ctypes.c_float * 3)
|
||||
(view_pos[0], view_pos[1], view_pos[2]))
|
||||
self.native_lib.SYMTEX_setViewDirection((ctypes.c_float * 3)
|
||||
(view_dir[0], view_dir[1], view_dir[2]))
|
||||
|
||||
num_verts = len(self.eval_mesh.vertices)
|
||||
vert_coords = np.empty(num_verts * 3, dtype=np.float32)
|
||||
vert_norms = np.empty(num_verts * 3, dtype=np.float32)
|
||||
|
||||
for i, vert in enumerate(self.eval_mesh.vertices):
|
||||
vert_coords[i * 3] = vert.co[0]
|
||||
vert_coords[i * 3 + 1] = vert.co[1]
|
||||
vert_coords[i * 3 + 2] = vert.co[2]
|
||||
|
||||
vert_norms[i * 3] = vert.normal[0]
|
||||
vert_norms[i * 3 + 1] = vert.normal[1]
|
||||
vert_norms[i * 3 + 2] = vert.normal[2]
|
||||
|
||||
self.native_lib.SYMTEX_setVertexCoords(vert_coords.ctypes.data_as(
|
||||
ctypes.POINTER(ctypes.c_float)), num_verts)
|
||||
self.native_lib.SYMTEX_setVertexNormals(vert_norms.ctypes.data_as(
|
||||
ctypes.POINTER(ctypes.c_float)), num_verts)
|
||||
|
||||
num_indices = len(self.eval_mesh.loops)
|
||||
vert_indices = np.empty(num_indices, dtype=np.int32)
|
||||
|
||||
for i, mesh_loop in enumerate(self.eval_mesh.loops):
|
||||
vert_indices[i] = mesh_loop.vertex_index
|
||||
|
||||
self.native_lib.SYMTEX_setVertexIndices(vert_indices.ctypes.data_as(
|
||||
ctypes.POINTER(ctypes.c_int)), num_indices)
|
||||
|
||||
num_uv_coords = len(self.eval_mesh.uv_layers.active.data)
|
||||
uv_coords = np.empty(num_uv_coords * 2, dtype=np.float32)
|
||||
|
||||
for i, loop_uv in enumerate(self.eval_mesh.uv_layers.active.data):
|
||||
uv_coords[i * 2] = loop_uv.uv[0]
|
||||
uv_coords[i * 2 + 1] = loop_uv.uv[1]
|
||||
|
||||
self.native_lib.SYMTEX_setUVCoords(uv_coords.ctypes.data_as(
|
||||
ctypes.POINTER(ctypes.c_float)), num_uv_coords)
|
||||
|
||||
self.eval_mesh.calc_loop_triangles()
|
||||
|
||||
num_triangles = len(self.eval_mesh.loop_triangles)
|
||||
tri_indices = np.empty(num_triangles * 3, dtype=np.int32)
|
||||
|
||||
for i, loop_tri in enumerate(self.eval_mesh.loop_triangles):
|
||||
tri_indices[i * 3] = loop_tri.loops[0]
|
||||
tri_indices[i * 3 + 1] = loop_tri.loops[1]
|
||||
tri_indices[i * 3 + 2] = loop_tri.loops[2]
|
||||
|
||||
self.native_lib.SYMTEX_setTriangles(tri_indices.ctypes.data_as(
|
||||
ctypes.POINTER(ctypes.c_int)), num_triangles)
|
||||
|
||||
img_width, img_height = self.image.size
|
||||
self.image_pixels = utils.read_pixels_from_image(self.image)
|
||||
|
||||
self.native_lib.SYMTEX_setImageSize(img_width, img_height)
|
||||
self.native_lib.SYMTEX_setImagePixels(self.image_pixels.ctypes.data_as(
|
||||
ctypes.POINTER(ctypes.c_float)))
|
||||
|
||||
if props.image_mirror_axis == 'x_axis':
|
||||
mirror_axis = 0
|
||||
else:
|
||||
mirror_axis = 1
|
||||
|
||||
self.native_lib.SYMTEX_setMirrorAxis(mirror_axis)
|
||||
|
||||
self.native_lib.SYMTEX_prepare()
|
||||
|
||||
session.draw_handler = bpy.types.SpaceView3D.draw_handler_add(
|
||||
app.draw_handler, (), 'WINDOW', 'POST_PIXEL')
|
||||
|
||||
context.window_manager.modal_handler_add(self)
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def process(self, context):
|
||||
session = app.get_session()
|
||||
|
||||
wm = context.window_manager
|
||||
props = wm.symmetrizetexture_properties
|
||||
|
||||
self.native_lib.SYMTEX_setBrushSize(ctypes.c_float(session.brush_size))
|
||||
self.native_lib.SYMTEX_setBrushStrength(ctypes.c_float(props.brush_strength))
|
||||
|
||||
if props.brush_falloff == 'smooth':
|
||||
falloff_type = 0
|
||||
else:
|
||||
falloff_type = 9
|
||||
|
||||
self.native_lib.SYMTEX_setBrushFalloffType(falloff_type)
|
||||
|
||||
pos = session.brush_position
|
||||
|
||||
self.native_lib.SYMTEX_processStroke((ctypes.c_float * 2)(*pos))
|
||||
|
||||
utils.write_pixels_to_image(self.image, self.image_pixels, False)
|
||||
|
||||
self.image.update()
|
||||
|
||||
class SYMMETRIZE_TEXTURE_OT_save_image(bpy.types.Operator):
|
||||
"""Save the image"""
|
||||
bl_idname = "image_layers_node.save_image"
|
||||
bl_label = "Save Image"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.mode == 'OBJECT'
|
||||
|
||||
def execute(self, context):
|
||||
wm = context.window_manager
|
||||
props = wm.symmetrizetexture_properties
|
||||
|
||||
img = bpy.data.images.get(props.image_preview)
|
||||
if not img or img.use_view_as_render:
|
||||
return {'CANCELLED'}
|
||||
|
||||
if img.packed_files:
|
||||
img.pack()
|
||||
elif img.filepath:
|
||||
img.save()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class SYMMETRIZE_TEXTURE_OT_mirrored_copy(bpy.types.Operator):
|
||||
"""Create a Mirrored copy of image"""
|
||||
bl_idname = "symmetrize_texture.mirrored_copy"
|
||||
bl_label = "Mirrored Copy"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.area.spaces.active.mode != 'UV' \
|
||||
and context.area.spaces.active.image != None \
|
||||
and not context.area.spaces.active.image.use_view_as_render
|
||||
|
||||
def modal(self, context, event):
|
||||
session = app.get_session()
|
||||
|
||||
wm = context.window_manager
|
||||
props = wm.symmetrizetexture_properties
|
||||
|
||||
context.area.tag_redraw()
|
||||
#context.tool_settings.image_paint.canvas = context.tool_settings.image_paint.canvas
|
||||
|
||||
region_pos = [event.mouse_region_x, event.mouse_region_y]
|
||||
|
||||
target_x, target_y = context.region.view2d.region_to_view(*region_pos)
|
||||
|
||||
if props.image_mirror_axis == 'x_axis':
|
||||
session.direction = target_x - 0.5
|
||||
else:
|
||||
session.direction = target_y - 0.5
|
||||
|
||||
if event.type in ['LEFTMOUSE']:
|
||||
self.process(context)
|
||||
session.selecting_direction = False
|
||||
|
||||
bpy.types.SpaceImageEditor.draw_handler_remove(session.draw_handler, 'WINDOW')
|
||||
|
||||
return {'FINISHED'}
|
||||
elif event.type in ['RIGHTMOUSE', 'ESC', 'RET']:
|
||||
session.selecting_direction = False
|
||||
|
||||
bpy.types.SpaceImageEditor.draw_handler_remove(session.draw_handler, 'WINDOW')
|
||||
|
||||
return {'CANCELLED'}
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
session = app.get_session()
|
||||
|
||||
if context.area.type != 'IMAGE_EDITOR':
|
||||
return {'CANCELLED'}
|
||||
|
||||
session.selecting_direction = True
|
||||
|
||||
session.draw_handler = bpy.types.SpaceImageEditor.draw_handler_add(
|
||||
app.draw_handler, (), 'WINDOW', 'POST_PIXEL')
|
||||
|
||||
context.window_manager.modal_handler_add(self)
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def process(self, context):
|
||||
session = app.get_session()
|
||||
|
||||
wm = context.window_manager
|
||||
props = wm.symmetrizetexture_properties
|
||||
|
||||
img = context.area.spaces.active.image
|
||||
|
||||
img_width, img_height = img.size
|
||||
|
||||
img_pixels = utils.read_pixels_from_image(img)
|
||||
|
||||
half_img_width = img_width / 2
|
||||
half_img_height = img_height / 2
|
||||
x_mid_1, x_mid_2 = int(math.floor(half_img_width)), int(math.ceil(half_img_width))
|
||||
y_mid_1, y_mid_2 = int(math.floor(half_img_height)), int(math.ceil(half_img_height))
|
||||
|
||||
if props.image_mirror_axis == 'x_axis':
|
||||
if session.direction > 0:
|
||||
mirror_pixels = img_pixels[:, :x_mid_1]
|
||||
img_pixels[:, x_mid_2:] = np.fliplr(mirror_pixels)
|
||||
else:
|
||||
mirror_pixels = img_pixels[:, x_mid_2:]
|
||||
img_pixels[:, :x_mid_1] = np.fliplr(mirror_pixels)
|
||||
else:
|
||||
if session.direction > 0:
|
||||
mirror_pixels = img_pixels[:y_mid_1]
|
||||
img_pixels[y_mid_2:] = np.flipud(mirror_pixels)
|
||||
else:
|
||||
mirror_pixels = img_pixels[y_mid_2:]
|
||||
img_pixels[:y_mid_1] = np.flipud(mirror_pixels)
|
||||
|
||||
utils.write_pixels_to_image(img, img_pixels, False)
|
||||
|
||||
img.update()
|
||||
|
||||
class SYMMETRIZE_TEXTURE_OT_use_2d_brush(bpy.types.Operator):
|
||||
"""Symmetrize the texture by using 2D brush"""
|
||||
bl_idname = "symmetrize_texture.use_2d_brush"
|
||||
bl_label = "Use 2D Brush"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def __init__(self):
|
||||
self.native_lib = None
|
||||
self.lmb = False
|
||||
self.image = None
|
||||
self.image_pixels = None
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.area.spaces.active.mode != 'UV' \
|
||||
and context.area.spaces.active.image != None \
|
||||
and not context.area.spaces.active.image.use_view_as_render
|
||||
|
||||
def modal(self, context, event):
|
||||
session = app.get_session()
|
||||
|
||||
context.area.tag_redraw()
|
||||
#context.tool_settings.image_paint.canvas = context.tool_settings.image_paint.canvas
|
||||
|
||||
region_pos = [event.mouse_region_x, event.mouse_region_y]
|
||||
|
||||
if session.resizing_brush:
|
||||
session.brush_position = self.resize_origin
|
||||
else:
|
||||
session.brush_position = region_pos
|
||||
|
||||
if event.type == 'MOUSEMOVE':
|
||||
if session.resizing_brush:
|
||||
session.brush_size = max(self.initial_brush_size
|
||||
+ region_pos[0] - self.resize_origin[0], 1.0)
|
||||
else:
|
||||
if self.lmb:
|
||||
self.process(context)
|
||||
else:
|
||||
pass
|
||||
#self.process(context, False)
|
||||
elif event.type == 'LEFTMOUSE':
|
||||
if event.value == 'PRESS':
|
||||
self.lmb = True
|
||||
|
||||
if session.resizing_brush:
|
||||
session.resizing_brush = False
|
||||
else:
|
||||
self.process(context)
|
||||
elif event.value == 'RELEASE':
|
||||
self.lmb = False
|
||||
|
||||
elif event.type in ['F']:
|
||||
if not session.resizing_brush:
|
||||
self.resize_origin = region_pos
|
||||
self.initial_brush_size = session.brush_size
|
||||
|
||||
session.resizing_brush = True
|
||||
elif event.type in ['RIGHTMOUSE', 'ESC', 'RET']:
|
||||
if event.value == 'PRESS':
|
||||
if session.resizing_brush:
|
||||
session.brush_size = self.initial_brush_size
|
||||
|
||||
session.resizing_brush = False
|
||||
else:
|
||||
session.brush_position = None
|
||||
session.brush_active = False
|
||||
session.resizing_brush = False
|
||||
|
||||
bpy.types.SpaceImageEditor.draw_handler_remove(session.draw_handler, 'WINDOW')
|
||||
|
||||
self.native_lib.SYMTEX_free()
|
||||
|
||||
app.unload_native_library(self.native_lib)
|
||||
|
||||
return {'CANCELLED'}
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
session = app.get_session()
|
||||
|
||||
wm = context.window_manager
|
||||
props = wm.symmetrizetexture_properties
|
||||
|
||||
if context.area.type != 'IMAGE_EDITOR':
|
||||
return {'CANCELLED'}
|
||||
|
||||
self.native_lib = app.load_native_library()
|
||||
|
||||
self.native_lib.SYMTEX_init(1) # 2D processor
|
||||
|
||||
session.brush_position = None
|
||||
session.brush_active = True
|
||||
|
||||
self.image = context.area.spaces.active.image
|
||||
|
||||
img_width, img_height = self.image.size
|
||||
self.image_pixels = utils.read_pixels_from_image(self.image)
|
||||
|
||||
self.native_lib.SYMTEX_setImageSize(img_width, img_height)
|
||||
self.native_lib.SYMTEX_setImagePixels(self.image_pixels.ctypes.data_as(
|
||||
ctypes.POINTER(ctypes.c_float)))
|
||||
|
||||
if props.image_mirror_axis == 'x_axis':
|
||||
mirror_axis = 0
|
||||
else:
|
||||
mirror_axis = 1
|
||||
|
||||
self.native_lib.SYMTEX_setMirrorAxis(mirror_axis)
|
||||
|
||||
self.native_lib.SYMTEX_prepare()
|
||||
|
||||
session.draw_handler = bpy.types.SpaceImageEditor.draw_handler_add(
|
||||
app.draw_handler, (), 'WINDOW', 'POST_PIXEL')
|
||||
|
||||
context.window_manager.modal_handler_add(self)
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def process(self, context):
|
||||
session = app.get_session()
|
||||
|
||||
wm = context.window_manager
|
||||
props = wm.symmetrizetexture_properties
|
||||
|
||||
view_x, view_y = context.region.view2d.region_to_view(*session.brush_position)
|
||||
|
||||
radius_x1, dummy = context.region.view2d.region_to_view(0, 0)
|
||||
radius_x2, dummy = context.region.view2d.region_to_view(session.brush_size, 0)
|
||||
radius = radius_x2 - radius_x1
|
||||
|
||||
self.native_lib.SYMTEX_setBrushSize(ctypes.c_float(radius))
|
||||
self.native_lib.SYMTEX_setBrushStrength(ctypes.c_float(props.brush_strength))
|
||||
|
||||
if props.brush_falloff == 'smooth':
|
||||
falloff_type = 0
|
||||
else:
|
||||
falloff_type = 9
|
||||
|
||||
self.native_lib.SYMTEX_setBrushFalloffType(falloff_type)
|
||||
|
||||
self.native_lib.SYMTEX_processStroke((ctypes.c_float * 2)(view_x, view_y))
|
||||
|
||||
utils.write_pixels_to_image(self.image, self.image_pixels, False)
|
||||
|
||||
self.image.update()
|
|
@ -0,0 +1,189 @@
|
|||
'''
|
||||
Copyright (C) 2021 - 2023 Akaneyu
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
import bpy
|
||||
from . import operators
|
||||
from . import app
|
||||
|
||||
def get_icon_id(icon_name):
|
||||
session = app.get_session()
|
||||
|
||||
if icon_name in session.icons:
|
||||
return session.icons[icon_name].icon_id
|
||||
else:
|
||||
return 0
|
||||
|
||||
def menu_func_3d(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.separator()
|
||||
|
||||
layout.menu(SYMMETRIZE_TEXTURE_MT_menu_3d.bl_idname, text='Symmetrize Texture')
|
||||
|
||||
def menu_func_2d(self, context):
|
||||
layout = self.layout
|
||||
|
||||
if context.area.spaces.active.mode != 'UV' \
|
||||
and context.area.spaces.active.image != None \
|
||||
and not context.area.spaces.active.image.use_view_as_render:
|
||||
|
||||
layout.separator()
|
||||
|
||||
layout.menu(SYMMETRIZE_TEXTURE_MT_menu_2d.bl_idname, text='Symmetrize Texture')
|
||||
|
||||
class SYMMETRIZE_TEXTURE_MT_menu_3d(bpy.types.Menu):
|
||||
bl_idname = "SYMMETRIZE_TEXTURE_MT_menu_3d"
|
||||
bl_label = "Symmetrize Texture"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.operator(operators.SYMMETRIZE_TEXTURE_OT_use_3d_brush.bl_idname, text='3D Brush',
|
||||
icon_value=get_icon_id('sym_brush'))
|
||||
|
||||
layout.operator(operators.SYMMETRIZE_TEXTURE_OT_save_image.bl_idname, text='Save Image',
|
||||
icon="FILE_TICK")
|
||||
|
||||
class SYMMETRIZE_TEXTURE_MT_menu_2d(bpy.types.Menu):
|
||||
bl_idname = "SYMMETRIZE_TEXTURE_MT_menu_2d"
|
||||
bl_label = "Symmetrize Texture"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.operator(operators.SYMMETRIZE_TEXTURE_OT_mirrored_copy.bl_idname, text='Mirrored Copy',
|
||||
icon_value=get_icon_id('mirror_copy'))
|
||||
|
||||
layout.operator(operators.SYMMETRIZE_TEXTURE_OT_use_2d_brush.bl_idname, text='2D Brush',
|
||||
icon_value=get_icon_id('sym_brush'))
|
||||
|
||||
class SYMMETRIZE_TEXTURE_PT_panel_3d(bpy.types.Panel):
|
||||
bl_label = "Symmetrize Texture"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "Symmetrize Texture"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.mode == 'OBJECT'
|
||||
|
||||
def draw(self, context):
|
||||
session = app.get_session()
|
||||
|
||||
wm = context.window_manager
|
||||
props = wm.symmetrizetexture_properties
|
||||
|
||||
if context.object != session.previous_object:
|
||||
imgs = self.find_texture_images(context)
|
||||
if imgs:
|
||||
props.image_preview = imgs[0].name
|
||||
|
||||
session.previous_object = context.object
|
||||
|
||||
layout = self.layout
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator(operators.SYMMETRIZE_TEXTURE_OT_use_3d_brush.bl_idname, text='3D Brush',
|
||||
icon_value=get_icon_id('sym_brush'))
|
||||
|
||||
row = layout.row()
|
||||
row.label(text='Texture:')
|
||||
row = layout.row()
|
||||
row.prop(wm.symmetrizetexture_properties, 'image_preview', text='')
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator(operators.SYMMETRIZE_TEXTURE_OT_save_image.bl_idname, text='Save Image',
|
||||
icon="FILE_TICK")
|
||||
|
||||
row = layout.row()
|
||||
row.label(text='Brush:')
|
||||
|
||||
row = layout.split(align=True)
|
||||
row.alignment = 'RIGHT'
|
||||
row.label(text='Strength')
|
||||
row.prop(props, "brush_strength", text='', slider=True)
|
||||
|
||||
row = layout.split(align=True)
|
||||
row.alignment = 'RIGHT'
|
||||
row.label(text='Falloff')
|
||||
row.prop(props, "brush_falloff", text='')
|
||||
|
||||
row = layout.row()
|
||||
row.label(text='Image Mirror Axis:')
|
||||
row = layout.row()
|
||||
row.prop(props, "image_mirror_axis", expand=True)
|
||||
|
||||
def find_texture_images(self, context):
|
||||
imgs = []
|
||||
for mat_slot in context.object.material_slots:
|
||||
self.find_texture_images_from_node_tree(imgs, mat_slot.material.node_tree)
|
||||
|
||||
return imgs
|
||||
|
||||
def find_texture_images_from_node_tree(self, imgs, node_tree):
|
||||
for node in node_tree.nodes:
|
||||
if node.bl_idname == 'ShaderNodeTexImage':
|
||||
if node.image not in imgs:
|
||||
imgs.append(node.image)
|
||||
elif node.bl_idname == 'ShaderNodeGroup':
|
||||
self.find_texture_images_from_node_tree(imgs, node.node_tree)
|
||||
|
||||
class SYMMETRIZE_TEXTURE_PT_panel_2d(bpy.types.Panel):
|
||||
bl_label = "Symmetrize Texture"
|
||||
bl_space_type = "IMAGE_EDITOR"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "Symmetrize Texture"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.area.spaces.active.mode != 'UV' \
|
||||
and context.area.spaces.active.image != None \
|
||||
and not context.area.spaces.active.image.use_view_as_render
|
||||
|
||||
def draw(self, context):
|
||||
session = app.get_session()
|
||||
|
||||
wm = context.window_manager
|
||||
props = wm.symmetrizetexture_properties
|
||||
|
||||
layout = self.layout
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator(operators.SYMMETRIZE_TEXTURE_OT_mirrored_copy.bl_idname, text='Mirrored Copy',
|
||||
icon_value=get_icon_id('mirror_copy'))
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator(operators.SYMMETRIZE_TEXTURE_OT_use_2d_brush.bl_idname, text='2D Brush',
|
||||
icon_value=get_icon_id('sym_brush'))
|
||||
|
||||
row = layout.row()
|
||||
row.label(text='Brush:')
|
||||
|
||||
row = layout.split(align=True)
|
||||
row.alignment = 'RIGHT'
|
||||
row.label(text='Strength')
|
||||
row.prop(props, "brush_strength", text='', slider=True)
|
||||
|
||||
row = layout.split(align=True)
|
||||
row.alignment = 'RIGHT'
|
||||
row.label(text='Falloff')
|
||||
row.prop(props, "brush_falloff", text='')
|
||||
|
||||
row = layout.row()
|
||||
row.label(text='Image Mirror Axis:')
|
||||
row = layout.row()
|
||||
row.prop(props, "image_mirror_axis", expand=True)
|
|
@ -0,0 +1,208 @@
|
|||
'''
|
||||
Copyright (C) 2020 Akaneyu
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
import time
|
||||
import bpy
|
||||
import bgl
|
||||
import blf
|
||||
import gpu
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
from mathutils import Matrix
|
||||
import numpy as np
|
||||
|
||||
default_vertex_shader = '''
|
||||
uniform mat4 ModelViewProjectionMatrix;
|
||||
|
||||
in vec2 pos;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = ModelViewProjectionMatrix * vec4(pos, 0, 1.0);
|
||||
}
|
||||
'''
|
||||
|
||||
default_fragment_shader = '''
|
||||
uniform vec4 color;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
fragColor = color;
|
||||
}
|
||||
'''
|
||||
|
||||
dotted_line_vertex_shader = '''
|
||||
uniform mat4 ModelViewProjectionMatrix;
|
||||
|
||||
in vec2 pos;
|
||||
in float arcLength;
|
||||
|
||||
out float arcLengthInter;
|
||||
|
||||
void main()
|
||||
{
|
||||
arcLengthInter = arcLength;
|
||||
|
||||
gl_Position = ModelViewProjectionMatrix * vec4(pos, 0, 1.0);
|
||||
}
|
||||
'''
|
||||
|
||||
dotted_line_fragment_shader = '''
|
||||
uniform float scale;
|
||||
uniform float offset;
|
||||
uniform vec4 color1;
|
||||
uniform vec4 color2;
|
||||
|
||||
in float arcLengthInter;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (step(sin((arcLengthInter + offset) * scale), 0.5) == 1) {
|
||||
fragColor = color1;
|
||||
} else {
|
||||
fragColor = color2;
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
class UIRenderer:
|
||||
def __init__(self):
|
||||
self.default_shader = gpu.types.GPUShader(default_vertex_shader,
|
||||
default_fragment_shader)
|
||||
self.default_shader_u_color = self.default_shader.uniform_from_name("color")
|
||||
|
||||
self.dotted_line_shader = gpu.types.GPUShader(dotted_line_vertex_shader,
|
||||
dotted_line_fragment_shader)
|
||||
self.dotted_line_shader_u_color1 = self.dotted_line_shader.uniform_from_name("color1")
|
||||
self.dotted_line_shader_u_color2 = self.dotted_line_shader.uniform_from_name("color2")
|
||||
|
||||
def render_border(self, pos1, pos2):
|
||||
bgl.glEnable(bgl.GL_BLEND)
|
||||
bgl.glLineWidth(1.0)
|
||||
|
||||
batch = batch_for_shader(self.default_shader, 'LINES',
|
||||
{"pos": [pos1, pos2]})
|
||||
|
||||
self.default_shader.bind()
|
||||
|
||||
self.default_shader.uniform_vector_float(self.default_shader_u_color,
|
||||
np.array([1.0, 0.0, 1.0, 1.0], 'f'), 4)
|
||||
|
||||
batch.draw(self.default_shader)
|
||||
|
||||
err = bgl.glGetError()
|
||||
if err != bgl.GL_NO_ERROR:
|
||||
print('render_border')
|
||||
print('OpenGL error:', err)
|
||||
|
||||
def render_arrow(self, center, angle):
|
||||
bgl.glEnable(bgl.GL_BLEND)
|
||||
bgl.glLineWidth(1.0)
|
||||
|
||||
gpu.matrix.load_identity()
|
||||
|
||||
with gpu.matrix.push_pop():
|
||||
gpu.matrix.translate(center)
|
||||
gpu.matrix.multiply_matrix(
|
||||
Matrix.Rotation(angle, 4, 'Z'))
|
||||
|
||||
verts = [
|
||||
(0, -50),
|
||||
(100, -50),
|
||||
(0, 50),
|
||||
(100, 50),
|
||||
(100, 0),
|
||||
(200, 0),
|
||||
(100, 100),
|
||||
(100, -100)
|
||||
]
|
||||
|
||||
indices = [
|
||||
(0, 1, 2),
|
||||
(2, 1, 3),
|
||||
(4, 5, 6),
|
||||
(4, 5, 7)
|
||||
]
|
||||
|
||||
batch = batch_for_shader(self.default_shader, 'TRIS',
|
||||
{"pos": verts}, indices=indices)
|
||||
|
||||
self.default_shader.bind()
|
||||
|
||||
self.default_shader.uniform_vector_float(self.default_shader_u_color,
|
||||
np.array([1.0, 1.0, 1.0, 0.5], 'f'), 4)
|
||||
|
||||
batch.draw(self.default_shader)
|
||||
|
||||
err = bgl.glGetError()
|
||||
if err != bgl.GL_NO_ERROR:
|
||||
print('render_arrow')
|
||||
print('OpenGL error:', err)
|
||||
|
||||
def render_brush_frame(self, pos, radius):
|
||||
bgl.glEnable(bgl.GL_BLEND)
|
||||
bgl.glLineWidth(2.0)
|
||||
|
||||
verts = self.create_brush_frame_vertices(pos, radius)
|
||||
|
||||
arc_lengths = [0]
|
||||
for a, b in zip(verts[:-1], verts[1:]):
|
||||
arc_lengths.append(arc_lengths[-1] + np.linalg.norm(a - b))
|
||||
|
||||
batch = batch_for_shader(self.dotted_line_shader, 'LINE_STRIP',
|
||||
{"pos": verts, "arcLength": arc_lengths})
|
||||
|
||||
self.dotted_line_shader.bind()
|
||||
|
||||
self.dotted_line_shader.uniform_float("scale", 0.6)
|
||||
self.dotted_line_shader.uniform_float("offset", 0)
|
||||
self.dotted_line_shader.uniform_vector_float(self.dotted_line_shader_u_color1,
|
||||
np.array([1.0, 1.0, 1.0, 0.5], 'f'), 4)
|
||||
self.dotted_line_shader.uniform_vector_float(self.dotted_line_shader_u_color2,
|
||||
np.array([0.0, 0.0, 0.0, 0.5], 'f'), 4)
|
||||
|
||||
batch.draw(self.dotted_line_shader)
|
||||
|
||||
err = bgl.glGetError()
|
||||
if err != bgl.GL_NO_ERROR:
|
||||
print('render_brush_frame')
|
||||
print('OpenGL error:', err)
|
||||
|
||||
def create_brush_frame_vertices(self, pos, radius):
|
||||
segs = 32
|
||||
|
||||
theta = 2.0 * np.pi / segs
|
||||
c = np.cos(theta)
|
||||
s = np.sin(theta)
|
||||
|
||||
x = radius
|
||||
y = 0
|
||||
|
||||
verts = []
|
||||
for i in range(segs):
|
||||
verts.append((x + pos[0], y + pos[1]))
|
||||
|
||||
t = x
|
||||
x = c * x - s * y
|
||||
y = s * t + c * y
|
||||
|
||||
verts.append(verts[0])
|
||||
|
||||
return np.array(verts, 'f')
|
|
@ -0,0 +1,38 @@
|
|||
'''
|
||||
Copyright (C) 2020 - 2022 Akaneyu
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
import bpy
|
||||
import numpy as np
|
||||
|
||||
def read_pixels_from_image(img):
|
||||
width, height = img.size[0], img.size[1]
|
||||
|
||||
if bpy.app.version >= (2, 83, 0):
|
||||
pixels = np.empty(len(img.pixels), dtype=np.float32);
|
||||
img.pixels.foreach_get(pixels)
|
||||
return np.reshape(pixels, (height, width, 4))
|
||||
else:
|
||||
return np.reshape(img.pixels[:], (height, width, 4))
|
||||
|
||||
def write_pixels_to_image(img, pixels, update_preview=True):
|
||||
if bpy.app.version >= (2, 83, 0):
|
||||
img.pixels.foreach_set(np.reshape(pixels, -1))
|
||||
else:
|
||||
img.pixels = np.reshape(pixels, -1)
|
||||
|
||||
if update_preview and img.preview:
|
||||
img.preview.reload()
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
Copyright (C) 2020 Akaneyu
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "math_util.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
void multiplyMatrix4fVector4f(float *out, float *mat, float *v)
|
||||
{
|
||||
float x = v[0];
|
||||
float y = v[1];
|
||||
float z = v[2];
|
||||
|
||||
out[0] = x * mat[0] + y * mat[1] + z * mat[2] + mat[3] * v[3];
|
||||
out[1] = x * mat[4] + y * mat[5] + z * mat[6] + mat[7] * v[3];
|
||||
out[2] = x * mat[8] + y * mat[9] + z * mat[10] + mat[11] * v[3];
|
||||
out[3] = x * mat[12] + y * mat[13] + z * mat[14] + mat[15] * v[3];
|
||||
}
|
||||
|
||||
void multiplyMatrix4fVector3f(float *out, float *mat, float *v)
|
||||
{
|
||||
float x = v[0];
|
||||
float y = v[1];
|
||||
float z = v[2];
|
||||
|
||||
out[0] = x * mat[0] + y * mat[1] + z * mat[2] + mat[3];
|
||||
out[1] = x * mat[4] + y * mat[5] + z * mat[6] + mat[7];
|
||||
out[2] = x * mat[8] + y * mat[9] + z * mat[10] + mat[11];
|
||||
}
|
||||
|
||||
void multiplyVector4fValue(float *out, float *v, float val)
|
||||
{
|
||||
out[0] = v[0] * val;
|
||||
out[1] = v[1] * val;
|
||||
out[2] = v[2] * val;
|
||||
out[3] = v[3] * val;
|
||||
}
|
||||
|
||||
void multiplyVector3fValue(float *out, float *v, float val)
|
||||
{
|
||||
out[0] = v[0] * val;
|
||||
out[1] = v[1] * val;
|
||||
out[2] = v[2] * val;
|
||||
}
|
||||
|
||||
void copyVector4f(float *out, float *v)
|
||||
{
|
||||
out[0] = v[0];
|
||||
out[1] = v[1];
|
||||
out[2] = v[2];
|
||||
out[3] = v[3];
|
||||
}
|
||||
|
||||
void copyVector4fValue(float *out, float val)
|
||||
{
|
||||
out[0] = val;
|
||||
out[1] = val;
|
||||
out[2] = val;
|
||||
out[3] = val;
|
||||
}
|
||||
|
||||
void copyVector3f(float *out, float *v)
|
||||
{
|
||||
out[0] = v[0];
|
||||
out[1] = v[1];
|
||||
out[2] = v[2];
|
||||
}
|
||||
|
||||
void copyVector3fValue(float *out, float val)
|
||||
{
|
||||
out[0] = val;
|
||||
out[1] = val;
|
||||
out[2] = val;
|
||||
}
|
||||
|
||||
void copyVector3i(int *out, int *v)
|
||||
{
|
||||
out[0] = v[0];
|
||||
out[1] = v[1];
|
||||
out[2] = v[2];
|
||||
}
|
||||
|
||||
void copyVector3iValue(int *out, int val)
|
||||
{
|
||||
out[0] = val;
|
||||
out[1] = val;
|
||||
out[2] = val;
|
||||
}
|
||||
|
||||
void copyVector2f(float *out, float *v)
|
||||
{
|
||||
out[0] = v[0];
|
||||
out[1] = v[1];
|
||||
}
|
||||
|
||||
void copyVector2i(int *out, int *v)
|
||||
{
|
||||
out[0] = v[0];
|
||||
out[1] = v[1];
|
||||
}
|
||||
|
||||
void copyVector2iValue(int *out, int val)
|
||||
{
|
||||
out[0] = val;
|
||||
out[1] = val;
|
||||
}
|
||||
|
||||
void subtractVector3f(float *out, float *v1, float *v2)
|
||||
{
|
||||
out[0] = v1[0] - v2[0];
|
||||
out[1] = v1[1] - v2[1];
|
||||
out[2] = v1[2] - v2[2];
|
||||
}
|
||||
|
||||
void normalizeVector3f(float *v)
|
||||
{
|
||||
float d = (float) sqrt(dotVector3f(v, v));
|
||||
|
||||
if (d == 0) {
|
||||
copyVector3fValue(v, 0);
|
||||
} else {
|
||||
multiplyVector3fValue(v, v, 1.0f / d);
|
||||
}
|
||||
}
|
||||
|
||||
float dotVector3f(float *v1, float *v2)
|
||||
{
|
||||
return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
|
||||
}
|
||||
|
||||
float crossTriVector2f(float *v1, float *v2, float *v3)
|
||||
{
|
||||
return (v1[0] - v2[0]) * (v2[1] - v3[1]) + (v1[1] - v2[1]) * (v3[0] - v2[0]);
|
||||
}
|
||||
|
||||
float lenSquaredVector2f(float *v1, float *v2)
|
||||
{
|
||||
float dx = v2[0] - v1[0];
|
||||
float dy = v2[1] - v1[1];
|
||||
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
void printVector2f(float *v) {
|
||||
cout << "(" << v[0] << ", " << v[1] << ")" << endl;
|
||||
}
|
||||
|
||||
void printVector4f(float *v) {
|
||||
cout << "(" << v[0] << ", " << v[1] << ", " << v[2] << ", " << v[3] << ")" << endl;
|
||||
}
|
||||
|
||||
void printMatrix4f(float *mat) {
|
||||
int i;
|
||||
|
||||
cout << "[" << endl;
|
||||
|
||||
for (i = 0; i < 16; i += 4) {
|
||||
cout << mat[i] << ", " << mat[i + 1] << ", " << mat[i + 2] << ", " << mat[i + 3] << endl;
|
||||
}
|
||||
|
||||
cout << "]" << endl;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Copyright (C) 2020 Akaneyu
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MATH_UTIL_H_
|
||||
#define MATH_UTIL_H_
|
||||
|
||||
const float PI = 3.1415927f;
|
||||
|
||||
void multiplyMatrix4fVector4f(float *out, float *mat, float *v);
|
||||
void multiplyMatrix4fVector3f(float *out, float *mat, float *v);
|
||||
void multiplyVector4fValue(float *out, float *v, float val);
|
||||
void multiplyVector3fValue(float *out, float *v, float val);
|
||||
void copyVector4f(float *out, float *v);
|
||||
void copyVector4fValue(float *out, float val);
|
||||
void copyVector3f(float *out, float *v);
|
||||
void copyVector3fValue(float *out, float val);
|
||||
void copyVector3i(int *out, int *v);
|
||||
void copyVector3iValue(int *out, int val);
|
||||
void copyVector2f(float *out, float *v);
|
||||
void copyVector2i(int *out, int *v);
|
||||
void copyVector2iValue(int *out, int val);
|
||||
void subtractVector3f(float *out, float *v1, float *v2);
|
||||
void normalizeVector3f(float *v);
|
||||
float dotVector3f(float *v1, float *v2);
|
||||
float crossTriVector2f(float *v1, float *v2, float *v3);
|
||||
float lenSquaredVector2f(float *v1, float *v2);
|
||||
void printVector2f(float *v);
|
||||
void printVector4f(float *v);
|
||||
void printMatrix4f(float *mat);
|
||||
|
||||
#endif // MATH_UTIL_H_
|
|
@ -0,0 +1,739 @@
|
|||
/*
|
||||
Copyright (C) 2020 - 2022 Akaneyu
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "symtex_processor.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
#include "math_util.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
Processor *g_processor;
|
||||
|
||||
int SYMTEX_init(int type)
|
||||
{
|
||||
g_processor = new Processor(type);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SYMTEX_free()
|
||||
{
|
||||
delete g_processor;
|
||||
}
|
||||
|
||||
void SYMTEX_setRegionSize(int width, int height)
|
||||
{
|
||||
g_processor->setRegionSize(width, height);
|
||||
}
|
||||
|
||||
void SYMTEX_setOrthogonal(int isOrtho)
|
||||
{
|
||||
g_processor->setOrthogonal(isOrtho != 0);
|
||||
}
|
||||
|
||||
void SYMTEX_setPerspectiveMatrix(float *mat)
|
||||
{
|
||||
g_processor->setPerspectiveMatrix(mat);
|
||||
}
|
||||
|
||||
void SYMTEX_setViewPosition(float *pos)
|
||||
{
|
||||
g_processor->setViewPosition(pos);
|
||||
}
|
||||
|
||||
void SYMTEX_setViewDirection(float *dir)
|
||||
{
|
||||
g_processor->setViewDirection(dir);
|
||||
}
|
||||
|
||||
void SYMTEX_setVertexCoords(float *coords, int numCoords)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < numCoords; i ++) {
|
||||
g_processor->addVertexCoord(coords + 3 * i);
|
||||
}
|
||||
}
|
||||
|
||||
void SYMTEX_setVertexNormals(float *normals, int numNormals)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < numNormals; i ++) {
|
||||
g_processor->addVertexNormal(normals + 3 * i);
|
||||
}
|
||||
}
|
||||
|
||||
void SYMTEX_setVertexIndices(int *indices, int numIndices)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < numIndices; i++) {
|
||||
g_processor->addVertexIndex(indices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void SYMTEX_setUVCoords(float *coords, int numCoords)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < numCoords; i++) {
|
||||
g_processor->addUVCoord(coords + 2 * i);
|
||||
}
|
||||
}
|
||||
|
||||
void SYMTEX_setTriangles(int *indices, int numTriangles)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < numTriangles; i++) {
|
||||
g_processor->addTriangle(indices + 3 * i);
|
||||
}
|
||||
}
|
||||
|
||||
void SYMTEX_setImageSize(int width, int height)
|
||||
{
|
||||
g_processor->setImageSize(width, height);
|
||||
}
|
||||
|
||||
void SYMTEX_setImagePixels(float *pixels)
|
||||
{
|
||||
g_processor->setImagePixels(pixels);
|
||||
}
|
||||
|
||||
void SYMTEX_setMirrorAxis(int axis)
|
||||
{
|
||||
g_processor->setMirrorAxis(axis);
|
||||
}
|
||||
|
||||
void SYMTEX_setBrushSize(float size)
|
||||
{
|
||||
g_processor->setBrushSize(size);
|
||||
}
|
||||
|
||||
void SYMTEX_setBrushStrength(float strength)
|
||||
{
|
||||
g_processor->setBrushStrength(strength);
|
||||
}
|
||||
|
||||
void SYMTEX_setBrushFalloffType(int type)
|
||||
{
|
||||
g_processor->setBrushFalloffType(type);
|
||||
}
|
||||
|
||||
void SYMTEX_prepare()
|
||||
{
|
||||
g_processor->prepare();
|
||||
}
|
||||
|
||||
void SYMTEX_processStroke(float *pos)
|
||||
{
|
||||
g_processor->processStroke(pos);
|
||||
}
|
||||
|
||||
static float checkLinePointSide2d(float *line1, float *line2, float *pt)
|
||||
{
|
||||
return ((line1[0] - pt[0]) * (line2[1] - pt[1]))
|
||||
- ((line2[0] - pt[0]) * (line1[1] - pt[1]));
|
||||
}
|
||||
|
||||
static bool checkIntersectPointPolygon2d(float *pt, float **verts, int numVerts)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (checkLinePointSide2d(verts[numVerts - 1], verts[0], pt) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = 1; i < numVerts; i++) {
|
||||
if (checkLinePointSide2d(verts[i - 1], verts[i], pt) < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void calcBarycentricWeights(float *weights, float *v1, float *v2, float *v3, float *coord)
|
||||
{
|
||||
weights[0] = crossTriVector2f(v2, v3, coord);
|
||||
weights[1] = crossTriVector2f(v3, v1, coord);
|
||||
weights[2] = crossTriVector2f(v1, v2, coord);
|
||||
|
||||
float totalWeight = weights[0] + weights[1] + weights[2];
|
||||
|
||||
if (totalWeight == 0) {
|
||||
copyVector3fValue(weights, 1.0f / 3.0f);
|
||||
} else {
|
||||
multiplyVector3fValue(weights, weights, 1.0f / totalWeight);
|
||||
}
|
||||
}
|
||||
|
||||
static void interpolateWeights3d(float *out, float *v1, float *v2, float *v3, float *weights)
|
||||
{
|
||||
out[0] = v1[0] * weights[0] + v2[0] * weights[1] + v3[0] * weights[2];
|
||||
out[1] = v1[1] * weights[0] + v2[1] * weights[1] + v3[1] * weights[2];
|
||||
out[2] = v1[2] * weights[0] + v2[2] * weights[1] + v3[2] * weights[2];
|
||||
}
|
||||
|
||||
static void calcScreenCoordOrthogonal(float *scrCoord, float *uv,
|
||||
float *v1ScrCoord, float *v2ScrCoord, float *v3ScrCoord,
|
||||
float *uv1Coord, float *uv2Coord, float *uv3Coord)
|
||||
{
|
||||
float weights[3];
|
||||
|
||||
calcBarycentricWeights(weights, uv1Coord, uv2Coord, uv3Coord, uv);
|
||||
interpolateWeights3d(scrCoord, v1ScrCoord, v2ScrCoord, v3ScrCoord, weights);
|
||||
}
|
||||
|
||||
static void calcScreenCoordPerspective(float *scrCoord, float *uv,
|
||||
float *v1ScrCoord, float *v2ScrCoord, float *v3ScrCoord,
|
||||
float *uv1Coord, float *uv2Coord, float *uv3Coord)
|
||||
{
|
||||
float weights[3];
|
||||
|
||||
calcBarycentricWeights(weights, uv1Coord, uv2Coord, uv3Coord, uv);
|
||||
|
||||
float weightsTemp[3];
|
||||
weightsTemp[0] = weights[0] * v1ScrCoord[3];
|
||||
weightsTemp[1] = weights[1] * v2ScrCoord[3];
|
||||
weightsTemp[2] = weights[2] * v3ScrCoord[3];
|
||||
|
||||
float totalWeight = weightsTemp[0] + weightsTemp[1] + weightsTemp[2];
|
||||
|
||||
if (totalWeight > 0) {
|
||||
float totalWeightInv = 1.0f / totalWeight;
|
||||
multiplyVector3fValue(weightsTemp, weightsTemp, totalWeightInv);
|
||||
} else {
|
||||
copyVector3fValue(weights, 1.0f / 3.0f);
|
||||
copyVector3fValue(weightsTemp, 1.0f / 3.0f);
|
||||
}
|
||||
|
||||
interpolateWeights3d(scrCoord, v1ScrCoord, v2ScrCoord, v3ScrCoord, weightsTemp);
|
||||
}
|
||||
|
||||
Triangle::Triangle()
|
||||
{
|
||||
copyVector3iValue(m_indices, 0);
|
||||
}
|
||||
|
||||
Triangle::Triangle(int *indices)
|
||||
{
|
||||
copyVector3i(m_indices, indices);
|
||||
}
|
||||
|
||||
Triangle::~Triangle()
|
||||
{
|
||||
}
|
||||
|
||||
PixelState::PixelState()
|
||||
{
|
||||
copyVector4fValue(m_screenCoord, 0);
|
||||
copyVector2iValue(m_imageCoord, 0);
|
||||
}
|
||||
|
||||
PixelState::~PixelState()
|
||||
{
|
||||
}
|
||||
|
||||
void PixelState::setScreenCoord(float *coord)
|
||||
{
|
||||
copyVector4f(m_screenCoord, coord);
|
||||
}
|
||||
|
||||
void PixelState::setImageCoord(int *coord)
|
||||
{
|
||||
copyVector2i(m_imageCoord, coord);
|
||||
}
|
||||
|
||||
Processor::Processor(int type) :
|
||||
m_processType(type),
|
||||
m_regionWidth(0),
|
||||
m_regionHeight(0),
|
||||
m_orthogonal(false),
|
||||
m_imageWidth(0),
|
||||
m_imageHeight(0),
|
||||
m_imagePixels(NULL),
|
||||
m_originalImagePixels(NULL),
|
||||
m_imageAlpha(NULL),
|
||||
m_mirrorAxis(0),
|
||||
m_brushSize(0),
|
||||
m_brushStrength(1.0f),
|
||||
m_brushFalloffType(0)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; i++) {
|
||||
m_perspectiveMatrix[i] = 0;
|
||||
}
|
||||
|
||||
copyVector3fValue(m_viewPosition, 0);
|
||||
copyVector3fValue(m_viewDirection, 0);
|
||||
}
|
||||
|
||||
Processor::~Processor()
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < (int) m_vertexCoords.size(); i++) {
|
||||
delete [] m_vertexCoords[i];
|
||||
}
|
||||
|
||||
for (i = 0; i < (int) m_vertexNormals.size(); i++) {
|
||||
delete [] m_vertexNormals[i];
|
||||
}
|
||||
|
||||
for (i = 0; i < (int) m_screenCoords.size(); i++) {
|
||||
delete [] m_screenCoords[i];
|
||||
}
|
||||
|
||||
for (i = 0; i < (int) m_uvCoords.size(); i++) {
|
||||
delete [] m_uvCoords[i];
|
||||
}
|
||||
|
||||
for (i = 0; i < (int) m_triangles.size(); i++) {
|
||||
delete m_triangles[i];
|
||||
}
|
||||
|
||||
if (m_originalImagePixels != NULL) {
|
||||
delete [] m_originalImagePixels;
|
||||
}
|
||||
|
||||
if (m_imageAlpha != NULL) {
|
||||
delete [] m_imageAlpha;
|
||||
}
|
||||
|
||||
for (i = 0; i < (int) m_pixelStates.size(); i++) {
|
||||
delete m_pixelStates[i];
|
||||
}
|
||||
}
|
||||
|
||||
void Processor::setRegionSize(int width, int height)
|
||||
{
|
||||
m_regionWidth = width;
|
||||
m_regionHeight = height;
|
||||
}
|
||||
|
||||
void Processor::setPerspectiveMatrix(float *mat)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; i++) {
|
||||
m_perspectiveMatrix[i] = mat[i];
|
||||
}
|
||||
}
|
||||
|
||||
void Processor::setViewPosition(float *pos)
|
||||
{
|
||||
copyVector3f(m_viewPosition, pos);
|
||||
}
|
||||
|
||||
void Processor::setViewDirection(float *dir)
|
||||
{
|
||||
copyVector3f(m_viewDirection, dir);
|
||||
}
|
||||
|
||||
void Processor::addVertexCoord(float *coord)
|
||||
{
|
||||
float *coordEntry = new float[3];
|
||||
copyVector3f(coordEntry, coord);
|
||||
|
||||
m_vertexCoords.push_back(coordEntry);
|
||||
}
|
||||
|
||||
void Processor::addVertexNormal(float *norm)
|
||||
{
|
||||
float *normEntry = new float[3];
|
||||
copyVector3f(normEntry, norm);
|
||||
|
||||
m_vertexNormals.push_back(normEntry);
|
||||
}
|
||||
|
||||
void Processor::addVertexIndex(int index)
|
||||
{
|
||||
m_vertexIndices.push_back(index);
|
||||
}
|
||||
|
||||
void Processor::addUVCoord(float *coord)
|
||||
{
|
||||
float *coordEntry = new float[2];
|
||||
copyVector2f(coordEntry, coord);
|
||||
|
||||
m_uvCoords.push_back(coordEntry);
|
||||
}
|
||||
|
||||
void Processor::addTriangle(int *indices)
|
||||
{
|
||||
Triangle *tri = new Triangle(indices);
|
||||
m_triangles.push_back(tri);
|
||||
}
|
||||
|
||||
void Processor::setImageSize(int width, int height)
|
||||
{
|
||||
m_imageWidth = width;
|
||||
m_imageHeight = height;
|
||||
}
|
||||
|
||||
void Processor::prepare()
|
||||
{
|
||||
int i, j, x, y;
|
||||
|
||||
long numPixels = m_imageWidth * m_imageHeight;
|
||||
long pixelBuffSize = numPixels * 4;
|
||||
|
||||
m_originalImagePixels = new float[pixelBuffSize];
|
||||
memcpy(m_originalImagePixels, m_imagePixels, sizeof(float) * pixelBuffSize);
|
||||
|
||||
m_imageAlpha = new unsigned short[numPixels];
|
||||
memset(m_imageAlpha, 0, sizeof(unsigned short) * numPixels);
|
||||
|
||||
if (m_processType != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
//cout << m_regionWidth << ", " << m_regionHeight << endl;
|
||||
//printMatrix4f(m_perspectiveMatrix);
|
||||
|
||||
const float normalAngleInner = 80.0f;
|
||||
const float normalAngle = (normalAngleInner + 90.0f) / 2.0f;
|
||||
const float normalAngleCos = (float) cos(normalAngle * PI / 180.0f);
|
||||
|
||||
float viewDirPersp[3];
|
||||
|
||||
for (i = 0; i < (int) m_vertexCoords.size(); i++) {
|
||||
float *vertCoord = m_vertexCoords[i];
|
||||
|
||||
float *scrCoord = new float[4];
|
||||
if (m_orthogonal) {
|
||||
multiplyMatrix4fVector3f(scrCoord, m_perspectiveMatrix, vertCoord);
|
||||
|
||||
scrCoord[0] = (m_regionWidth * 0.5f) + (m_regionWidth * 0.5f) * scrCoord[0];
|
||||
scrCoord[1] = (m_regionHeight * 0.5f) + (m_regionHeight * 0.5f) * scrCoord[1];
|
||||
} else {
|
||||
copyVector3f(scrCoord, vertCoord);
|
||||
scrCoord[3] = 1.0f;
|
||||
multiplyMatrix4fVector4f(scrCoord, m_perspectiveMatrix, scrCoord);
|
||||
|
||||
scrCoord[0] = m_regionWidth * 0.5f
|
||||
+ m_regionWidth * 0.5f * scrCoord[0] / scrCoord[3];
|
||||
scrCoord[1] = m_regionHeight * 0.5f
|
||||
+ m_regionHeight * 0.5f * scrCoord[1] / scrCoord[3];
|
||||
scrCoord[2] = scrCoord[2] / scrCoord[3];
|
||||
}
|
||||
|
||||
m_screenCoords.push_back(scrCoord);
|
||||
|
||||
float *vertNorm = m_vertexNormals[i];
|
||||
|
||||
int vertState = 0;
|
||||
if (m_orthogonal) {
|
||||
if (dotVector3f(m_viewDirection, vertNorm) <= normalAngleCos) {
|
||||
vertState |= 1;
|
||||
}
|
||||
} else {
|
||||
subtractVector3f(viewDirPersp, m_viewPosition, vertCoord);
|
||||
normalizeVector3f(viewDirPersp);
|
||||
|
||||
if (dotVector3f(viewDirPersp, vertNorm) <= normalAngleCos) {
|
||||
vertState |= 1;
|
||||
}
|
||||
}
|
||||
|
||||
m_vertexStates.push_back(vertState);
|
||||
}
|
||||
|
||||
int triVertIndices[3];
|
||||
float *triUvCoords[3];
|
||||
float minUvCoord[2];
|
||||
float maxUvCoord[2];
|
||||
float uvCoord[2];
|
||||
int minImgRect[2];
|
||||
int maxImgRect[2];
|
||||
|
||||
for (i = 0; i < (int) m_triangles.size(); i++) {
|
||||
Triangle *tri = m_triangles[i];
|
||||
int *triIndices = tri->getIndices();
|
||||
|
||||
triVertIndices[0] = m_vertexIndices[triIndices[0]];
|
||||
triVertIndices[1] = m_vertexIndices[triIndices[1]];
|
||||
triVertIndices[2] = m_vertexIndices[triIndices[2]];
|
||||
|
||||
bool culled = true;
|
||||
for (j = 0; j < 3; j++) {
|
||||
int vertState = m_vertexStates[triVertIndices[j]];
|
||||
if ((vertState & 1) == 0) {
|
||||
culled = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (culled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float *v1ScrCoord = m_screenCoords[triVertIndices[0]];
|
||||
float *v2ScrCoord = m_screenCoords[triVertIndices[1]];
|
||||
float *v3ScrCoord = m_screenCoords[triVertIndices[2]];
|
||||
|
||||
//cout << "Triangle #" << i << endl;
|
||||
|
||||
//printVector4f(v1ScrCoord);
|
||||
//printVector4f(v2ScrCoord);
|
||||
//printVector4f(v3ScrCoord);
|
||||
|
||||
triUvCoords[0] = m_uvCoords[triIndices[0]];
|
||||
triUvCoords[1] = m_uvCoords[triIndices[1]];
|
||||
triUvCoords[2] = m_uvCoords[triIndices[2]];
|
||||
|
||||
//printVector2f(triUvCoords[0]);
|
||||
//printVector2f(triUvCoords[1]);
|
||||
//printVector2f(triUvCoords[2]);
|
||||
|
||||
minUvCoord[0] = numeric_limits<float>::max();
|
||||
minUvCoord[1] = numeric_limits<float>::max();
|
||||
maxUvCoord[0] = -numeric_limits<float>::max();
|
||||
maxUvCoord[1] = -numeric_limits<float>::max();
|
||||
|
||||
for (j = 0; j < 3; j++) {
|
||||
minUvCoord[0] = triUvCoords[j][0] < minUvCoord[0] ? triUvCoords[j][0] : minUvCoord[0];
|
||||
minUvCoord[1] = triUvCoords[j][1] < minUvCoord[1] ? triUvCoords[j][1] : minUvCoord[1];
|
||||
maxUvCoord[0] = triUvCoords[j][0] > maxUvCoord[0] ? triUvCoords[j][0] : maxUvCoord[0];
|
||||
maxUvCoord[1] = triUvCoords[j][1] > maxUvCoord[1] ? triUvCoords[j][1] : maxUvCoord[1];
|
||||
}
|
||||
|
||||
minImgRect[0] = (int) (m_imageWidth * minUvCoord[0]);
|
||||
minImgRect[1] = (int) (m_imageHeight * minUvCoord[1]);
|
||||
maxImgRect[0] = (int) (m_imageWidth * maxUvCoord[0] + 1);
|
||||
maxImgRect[1] = (int) (m_imageHeight * maxUvCoord[1] + 1);
|
||||
|
||||
//cout << minImgRect[0] << ", " << minImgRect[1] << endl;
|
||||
//cout << maxImgRect[0] << ", " << maxImgRect[1] << endl;
|
||||
|
||||
for (y = minImgRect[1]; y < maxImgRect[1]; y++) {
|
||||
uvCoord[1] = y / (float) m_imageHeight;
|
||||
|
||||
for (x = minImgRect[0]; x < maxImgRect[0]; x++) {
|
||||
uvCoord[0] = x / (float) m_imageWidth;
|
||||
|
||||
preparePixel(x, y, uvCoord, v1ScrCoord, v2ScrCoord, v3ScrCoord, triUvCoords);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Processor::processStroke(float *pos)
|
||||
{
|
||||
if (m_processType == 0) {
|
||||
processStroke3d(pos);
|
||||
} else {
|
||||
processStroke2d(pos);
|
||||
}
|
||||
}
|
||||
|
||||
void Processor::preparePixel(int x, int y, float *uvCoord,
|
||||
float *v1ScrCoord, float *v2ScrCoord, float *v3ScrCoord, float **triUvCoords)
|
||||
{
|
||||
if (!checkIntersectPointPolygon2d(uvCoord, triUvCoords, 3)) {
|
||||
return;
|
||||
}
|
||||
|
||||
float pixelScrCoord[4];
|
||||
if (m_orthogonal) {
|
||||
calcScreenCoordOrthogonal(pixelScrCoord,
|
||||
uvCoord, v1ScrCoord, v2ScrCoord, v3ScrCoord,
|
||||
triUvCoords[0], triUvCoords[1], triUvCoords[2]);
|
||||
} else {
|
||||
calcScreenCoordPerspective(pixelScrCoord,
|
||||
uvCoord, v1ScrCoord, v2ScrCoord, v3ScrCoord,
|
||||
triUvCoords[0], triUvCoords[1], triUvCoords[2]);
|
||||
}
|
||||
|
||||
x = x % m_imageWidth;
|
||||
y = y % m_imageHeight;
|
||||
|
||||
int imgCoord[] = {x, y};
|
||||
|
||||
PixelState *pixelState = new PixelState();
|
||||
pixelState->setScreenCoord(pixelScrCoord);
|
||||
pixelState->setImageCoord(imgCoord);
|
||||
|
||||
m_pixelStates.push_back(pixelState);
|
||||
}
|
||||
|
||||
void Processor::processStroke3d(float *pos)
|
||||
{
|
||||
int i;
|
||||
|
||||
float brushRadiusSq = m_brushSize * m_brushSize;
|
||||
|
||||
for (i = 0; i < (int) m_pixelStates.size(); i++) {
|
||||
PixelState *pixelState = m_pixelStates[i];
|
||||
|
||||
float distSq = lenSquaredVector2f(pos, pixelState->getScreenCoord());
|
||||
|
||||
if (distSq >= brushRadiusSq) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int *imgCoord = pixelState->getImageCoord();
|
||||
|
||||
float dist = (float) sqrt(distSq);
|
||||
float falloff = calcBrushFalloff(dist, m_brushSize);
|
||||
|
||||
processStrokePixel(imgCoord[0], imgCoord[1], falloff);
|
||||
}
|
||||
}
|
||||
|
||||
void Processor::processStroke2d(float *pos)
|
||||
{
|
||||
int x, y;
|
||||
|
||||
int px = (int) (m_imageWidth * pos[0]);
|
||||
int py = (int) (m_imageHeight * pos[1]);
|
||||
|
||||
int brushRadius = (int) (m_imageWidth * m_brushSize);
|
||||
|
||||
int x1 = px - brushRadius;
|
||||
int y1 = py - brushRadius;
|
||||
int x2 = px + brushRadius;
|
||||
int y2 = py + brushRadius;
|
||||
|
||||
x1 = max(min(x1, m_imageWidth), 0);
|
||||
y1 = max(min(y1, m_imageHeight), 0);
|
||||
x2 = max(min(x2, m_imageWidth), 0);
|
||||
y2 = max(min(y2, m_imageHeight), 0);
|
||||
|
||||
float brushRadiusSq = brushRadius * (float) brushRadius;
|
||||
|
||||
for (y = y1; y < y2; y++) {
|
||||
for (x = x1; x < x2; x++) {
|
||||
float distSq = (px - x) * (float) (px - x) + (py - y) * (float) (py - y);
|
||||
|
||||
if (distSq >= brushRadiusSq) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float dist = (float) sqrt(distSq);
|
||||
float falloff = calcBrushFalloff(dist, (float) brushRadius);
|
||||
|
||||
processStrokePixel(x, y, falloff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Processor::processStrokePixel(int x, int y, float falloff)
|
||||
{
|
||||
long alphaOffset = m_imageWidth * y + x;
|
||||
|
||||
float alphaTemp = falloff * m_brushStrength;
|
||||
|
||||
float alphaSaved = m_imageAlpha[alphaOffset] / 65535.0f;
|
||||
float alpha = (alphaTemp - alphaSaved * falloff) + alphaSaved;
|
||||
|
||||
if (alpha > 1.0f) {
|
||||
alpha = 1.0f;
|
||||
}
|
||||
|
||||
if (alpha > alphaSaved) {
|
||||
m_imageAlpha[alphaOffset] = (unsigned short) (alpha * 65535.0f);
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (alpha <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
long pixelOffset = (m_imageWidth * y + x) * 4;
|
||||
|
||||
float *addr = m_imagePixels + pixelOffset;
|
||||
float *origAddr = m_originalImagePixels + pixelOffset;
|
||||
|
||||
int mirrorOffset = 0;
|
||||
if (m_mirrorAxis == 0) {
|
||||
mirrorOffset = (m_imageWidth * y + (m_imageWidth - x)) * 4;
|
||||
} else {
|
||||
mirrorOffset = (m_imageWidth * (m_imageHeight - y - 1) + x) * 4;
|
||||
}
|
||||
|
||||
float *mirrorAddr = m_imagePixels + mirrorOffset;
|
||||
|
||||
float srcColor[4];
|
||||
float destColor[4];
|
||||
|
||||
srcColor[0] = *mirrorAddr;
|
||||
srcColor[1] = *(mirrorAddr + 1);
|
||||
srcColor[2] = *(mirrorAddr + 2);
|
||||
srcColor[3] = *(mirrorAddr + 3);
|
||||
|
||||
//srcColor[0] = 1.0f;
|
||||
//srcColor[1] = 0;
|
||||
//srcColor[2] = 0;
|
||||
//srcColor[3] = 1.0f;
|
||||
|
||||
destColor[0] = *origAddr;
|
||||
destColor[1] = *(origAddr + 1);
|
||||
destColor[2] = *(origAddr + 2);
|
||||
destColor[3] = *(origAddr + 3);
|
||||
|
||||
multiplyVector4fValue(srcColor, srcColor, alpha);
|
||||
//srcColor[3] = alpha;
|
||||
|
||||
if (srcColor[3] > 0) {
|
||||
float t = srcColor[3];
|
||||
float tComp = 1.0f - t;
|
||||
|
||||
destColor[0] = tComp * destColor[0] + srcColor[0];
|
||||
destColor[1] = tComp * destColor[1] + srcColor[1];
|
||||
destColor[2] = tComp * destColor[2] + srcColor[2];
|
||||
destColor[3] = tComp * destColor[3] + t;
|
||||
|
||||
*addr = destColor[0];
|
||||
*(addr + 1) = destColor[1];
|
||||
*(addr + 2) = destColor[2];
|
||||
*(addr + 3) = destColor[3];
|
||||
}
|
||||
}
|
||||
|
||||
float Processor::calcBrushFalloff(float dist, float len)
|
||||
{
|
||||
float falloff = 1.0f; // default: constant
|
||||
|
||||
if (dist >= len) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
float p = 1.0f - dist / len;
|
||||
|
||||
if (m_brushFalloffType == 0) { // smooth
|
||||
falloff = 3.0f * p * p - 2.0f * p * p * p;
|
||||
}
|
||||
|
||||
if (falloff < 0) {
|
||||
falloff = 0;
|
||||
} else if (falloff > 1.0f) {
|
||||
falloff = 1.0f;
|
||||
}
|
||||
|
||||
return falloff;
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
Copyright (C) 2020 - 2022 Akaneyu
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SYMTEX_PROCESSOR_H_
|
||||
#define SYMTEX_PROCESSOR_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define EXPORT
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
|
||||
EXPORT int SYMTEX_init(int type);
|
||||
EXPORT void SYMTEX_free();
|
||||
EXPORT void SYMTEX_setRegionSize(int width, int height);
|
||||
EXPORT void SYMTEX_setOrthogonal(int isOrtho);
|
||||
EXPORT void SYMTEX_setPerspectiveMatrix(float *mat);
|
||||
EXPORT void SYMTEX_setViewPosition(float *pos);
|
||||
EXPORT void SYMTEX_setViewDirection(float *dir);
|
||||
EXPORT void SYMTEX_setVertexCoords(float *coords, int numCoords);
|
||||
EXPORT void SYMTEX_setVertexNormals(float *normals, int numNormals);
|
||||
EXPORT void SYMTEX_setVertexIndices(int *indices, int numIndices);
|
||||
EXPORT void SYMTEX_setUVCoords(float *coords, int numCoords);
|
||||
EXPORT void SYMTEX_setTriangles(int *indices, int numTriangles);
|
||||
EXPORT void SYMTEX_setImageSize(int width, int height);
|
||||
EXPORT void SYMTEX_setImagePixels(float *pixels);
|
||||
EXPORT void SYMTEX_setMirrorAxis(int axis);
|
||||
EXPORT void SYMTEX_setBrushSize(float size);
|
||||
EXPORT void SYMTEX_setBrushStrength(float strength);
|
||||
EXPORT void SYMTEX_setBrushFalloffType(int type);
|
||||
EXPORT void SYMTEX_prepare();
|
||||
EXPORT void SYMTEX_processStroke(float *pos);
|
||||
|
||||
}
|
||||
|
||||
class Triangle
|
||||
{
|
||||
public:
|
||||
Triangle();
|
||||
Triangle(int *indices);
|
||||
virtual ~Triangle();
|
||||
int *getIndices() { return m_indices; }
|
||||
|
||||
private:
|
||||
int m_indices[3];
|
||||
};
|
||||
|
||||
class PixelState
|
||||
{
|
||||
public:
|
||||
PixelState();
|
||||
virtual ~PixelState();
|
||||
float *getScreenCoord() { return m_screenCoord; }
|
||||
void setScreenCoord(float *coord);
|
||||
int *getImageCoord() { return m_imageCoord; }
|
||||
void setImageCoord(int *coord);
|
||||
|
||||
private:
|
||||
float m_screenCoord[4];
|
||||
int m_imageCoord[2];
|
||||
};
|
||||
|
||||
// brush position & size
|
||||
// 3D: screen coordinate, 2D: uv coordinate (0 - 1.0)
|
||||
|
||||
class Processor
|
||||
{
|
||||
public:
|
||||
Processor(int type);
|
||||
virtual ~Processor();
|
||||
void setRegionSize(int width, int height);
|
||||
void setOrthogonal(bool isOrtho) { m_orthogonal = isOrtho; }
|
||||
void setPerspectiveMatrix(float *mat);
|
||||
void setViewPosition(float *pos);
|
||||
void setViewDirection(float *dir);
|
||||
void addVertexCoord(float *coord);
|
||||
void addVertexNormal(float *norm);
|
||||
void addVertexIndex(int index);
|
||||
void addUVCoord(float *coord);
|
||||
void addTriangle(int *indices);
|
||||
void setImageSize(int width, int height);
|
||||
void setImagePixels(float *pixels) { m_imagePixels = pixels; }
|
||||
void setMirrorAxis(int axis) { m_mirrorAxis = axis; }
|
||||
void setBrushSize(float size) { m_brushSize = size; }
|
||||
void setBrushStrength(float strength) { m_brushStrength = strength; }
|
||||
void setBrushFalloffType(int type) { m_brushFalloffType = type; }
|
||||
void prepare();
|
||||
void processStroke(float *pos);
|
||||
|
||||
private:
|
||||
void preparePixel(int x, int y, float *uvCoord,
|
||||
float *v1ScrCoord, float *v2ScrCoord, float *v3ScrCoord, float **triUvCoords);
|
||||
void processStroke3d(float *pos);
|
||||
void processStroke2d(float *pos);
|
||||
void processStrokePixel(int x, int y, float falloff);
|
||||
float calcBrushFalloff(float dist, float len);
|
||||
int m_processType;
|
||||
int m_regionWidth;
|
||||
int m_regionHeight;
|
||||
bool m_orthogonal;
|
||||
float m_perspectiveMatrix[16];
|
||||
float m_viewPosition[3];
|
||||
float m_viewDirection[3];
|
||||
std::vector<float *> m_vertexCoords;
|
||||
std::vector<float *> m_vertexNormals;
|
||||
std::vector<float *> m_screenCoords;
|
||||
std::vector<int> m_vertexIndices;
|
||||
std::vector<float *> m_uvCoords;
|
||||
std::vector<Triangle *> m_triangles;
|
||||
std::vector<int> m_vertexStates;
|
||||
int m_imageWidth;
|
||||
int m_imageHeight;
|
||||
float *m_imagePixels;
|
||||
float *m_originalImagePixels;
|
||||
unsigned short *m_imageAlpha;
|
||||
std::vector<PixelState *> m_pixelStates;
|
||||
int m_mirrorAxis;
|
||||
float m_brushSize;
|
||||
float m_brushStrength;
|
||||
int m_brushFalloffType;
|
||||
};
|
||||
|
||||
#endif // SYMTEX_PROCESSOR_H_
|
Loading…
Reference in New Issue