From 995bf859b52d1e211e6d21c0b670c27b47a31899 Mon Sep 17 00:00:00 2001 From: akaneyu Date: Tue, 5 Mar 2024 21:33:30 +0900 Subject: [PATCH] Initial commit --- .gitignore | 2 + CMakeLists.txt | 13 + addon/__init__.py | 88 +++++ addon/app.py | 166 ++++++++ addon/icons/mirror_copy.png | Bin 0 -> 793 bytes addon/icons/mirror_x.png | Bin 0 -> 581 bytes addon/icons/mirror_y.png | Bin 0 -> 588 bytes addon/icons/sym_brush.png | Bin 0 -> 926 bytes addon/operators.py | 533 ++++++++++++++++++++++++++ addon/ui.py | 189 +++++++++ addon/ui_renderer.py | 208 ++++++++++ addon/utils.py | 38 ++ src/math_util.cpp | 179 +++++++++ src/math_util.h | 45 +++ src/symtex_processor.cpp | 739 ++++++++++++++++++++++++++++++++++++ src/symtex_processor.h | 141 +++++++ 16 files changed, 2341 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 addon/__init__.py create mode 100644 addon/app.py create mode 100644 addon/icons/mirror_copy.png create mode 100644 addon/icons/mirror_x.png create mode 100644 addon/icons/mirror_y.png create mode 100644 addon/icons/sym_brush.png create mode 100644 addon/operators.py create mode 100644 addon/ui.py create mode 100644 addon/ui_renderer.py create mode 100644 addon/utils.py create mode 100644 src/math_util.cpp create mode 100644 src/math_util.h create mode 100644 src/symtex_processor.cpp create mode 100644 src/symtex_processor.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b958328 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/build*/ +__pycache__/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..457cdca --- /dev/null +++ b/CMakeLists.txt @@ -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 +) diff --git a/addon/__init__.py b/addon/__init__.py new file mode 100644 index 0000000..7623a7d --- /dev/null +++ b/addon/__init__.py @@ -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 . +''' + +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() diff --git a/addon/app.py b/addon/app.py new file mode 100644 index 0000000..07b0990 --- /dev/null +++ b/addon/app.py @@ -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 . +''' + +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() diff --git a/addon/icons/mirror_copy.png b/addon/icons/mirror_copy.png new file mode 100644 index 0000000000000000000000000000000000000000..a0c738ca75749480f8187b2948761b13ae3e9f96 GIT binary patch literal 793 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-G$+Qd;gjJKptm- zM`SV3z?~q>DERQKI#7_k#M9T6{Rs!Z2&+!*B?;{*2b9{e$ z$Xb<#V?Xp2GJANuj#!w@TF__4lIq|qGh+@%Gk3S4phQehV$#A+%iKp9Q(7I9V>l-B zO=(?{n4!QZv~~fjmpxBRb@`p$+dO1y_CLM%y|#XS_4m5{r#D|X=ysR!^M6}~)gSjW z?#S9K^^MtMh3_PjC7HGpopQJ@=2S&wdbt( zm>b*%$K}N`IakaTsd&v=a#4&a;<(u>O*6}POv`3+Gn!mZxKiDi*AjdzIEqX2vu>=} zD@o7vyLK~zRag?T53GnUQCKxyr8GPGw>1CM>pi=hKYZz##+$WU$m?bMe&O{6yS+T` z{x~5v{ZDN{N9CqD91-n%b6+2ceUQ3A_zHhqnxmURDp2bzm z-BhP=|Lc9WLGr^HK4VUvD%pcmEc%@jj#u#a{JOf}E%yYKD7*KH&sWZUf1rDB-R0`I z;0L?j3oN#IsJ{Ot|GU$BZm#A?G(5v*tP;t(&oaxm;qD8Dutr_G;`vsaPp_&lo*TIO z)2WQLP0PI`H}`~>h3t;7%iOl<;IIGs*JV`&@{c|&{$~PA1*#>k5hW>!C8<`)MX5lF z!N|bKK-a)R*U&V?(9p`j)XKy}+rZMwz#zE(_cs&`x%nxXX_dG&C@DF60X0a1YzWRz qD=AMbN@XZ7FW1Y=%Pvk%EJ)SMFG`>N&PEETh{4m<&t;ucLK6T&NH!k; literal 0 HcmV?d00001 diff --git a/addon/icons/mirror_x.png b/addon/icons/mirror_x.png new file mode 100644 index 0000000000000000000000000000000000000000..7927a0977abbec62b77fa173b3e964bf3a1d458a GIT binary patch literal 581 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-G$+Qd;gjJKptm- zM`SV3z?~q>DERQKI#7_k#M9T6{Rs!Z2&*pFRBn3)21a2|7sn8f<6Ea1_8l@1aJAQA z6%rCEU!Y*C{!pky@vfLiNC0!Vhoh&r_vXwRhOWKGyfV$^Gl>|<9oFc|F`qZ_|76?G zkH2)}ExsQpFF!%~|DMDIhDk5NH~4MX%&_-HZb?Bdqo!GYdH#e|GEaRXH$K?VCmXw0 zZd1b15UnLOC%f!r%#AoFyO}(iDjNQtVdkXY4eU*S_!ymvDq9sEZMI?gAiyubX2Ws$ zx{vF#Y?upzZ|eIPI<+;wJtDp4;)?x`U1ZA-ww~&Z+*73(y+Uvoo0Hyc38x zT%Men8dGs`Q&BP7+BfA7ITD&S^<8J!cD}Te+t0$Q@vEE#!{@U3#itV`U*<3_vd?^0 zzp$8hQM{tv13tEu6AibfEnf%>G1U^+h?11Vl2ohYqEsNoU}Ruqple{EYiJr`XlP|% zYGq=oZD46-VDOdWgAT0%Yk0X0a1YzWRzD=AMbN@XZ7FW1Y=%Pvk% aEJ)SMFG`>N&PEETh{4m<&t;ucLK6VgKgbmT literal 0 HcmV?d00001 diff --git a/addon/icons/mirror_y.png b/addon/icons/mirror_y.png new file mode 100644 index 0000000000000000000000000000000000000000..668d9282b9877e76d6f93d56fe3b96f724c88545 GIT binary patch literal 588 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-G$+Qd;gjJKptm- zM`SV3z?~q>DERQKI#7_k#M9T6{Rs!Z2&*pFRBn3)21Y4Q7sn8f<7cPZ`WO>C;Zi##?T+U_x(sa2_0p4Fb~#=O!eEQZt8 zT+O(*eCqeN#Y(@f2Kv{EOqlGh_QZClistvUn#!eHudT9a(VxHaO82r!@5)&3thnT} zvZ=|>B=>%+zPqDi1xNfp)&c?F;}xfxxZ~=-+}n}j7$)y0HP<5H^G>bq+73yFnXIi>$pDR>vOue^1vIm%E370jFBx8c~vxSdwa$T$Bo=7>o>z40H`F zbPY{I3=OReOsz~zwGAw-3=F<9nO2EgLrbW~KcEIlkPX54X(i=}MX3zs j<>h*rdD+Fui3O>8`9k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-G$+Qd;gjJKptm- zM`SV3z?~q>DERQKI#7_k#M9T6{Rs!Z2&+!*BMc^{NwX` zhA!?oMTy-aZc3Sgn*X9RlsOhn$Xn%F?+_5vw!pcs7`(2Vw^uKGoJo%Hzg-1Glv!XYr zzck9bQMEc|;)`t_8n2Hgo9pJXyg9nBZS}1O>y9|{?+M?%D=%b?*{fMUBM+?p_jPX2 z<=pjV&-(i2?9s`YvLc1?_l6}OB;Q-#RN-HEy@gdX;lTUgBaXALF;%`ea9s02S8*`I zy{)SpqvhJpb`uwaW5^Hg67Xt5G;xcJ+STs>sthD^If5UQmgDv2OnC;)l7N#$Kna?iyq< zOsz6nTHE&d*xC%9rviKr78Gx-dBXmb=|Ek{va+(wWve&kUJaR_X^Y!-5 z{1}_79A;K=U+-hEZL)F0tDT>9KhC`H{BqTy@|gx1adJ|JivIHMdpJq#W&f5Nm2+0^ z59a$Y?X>SFPM)8Kl<(hKbA3kPTvpC_>qE2HTpoE>|7na)xMLot@3;M=quj;yt{nQW zFFaj*^8WM82Aw1o-pQJ;oR%#weVCNfZLjPk(;5G2^Znx&>aVPj$?rY=^{sufzG=ww zt%dq^weyrBXXRDaGxh%#+q-Sut^#0ER4s9hC`m~yNwrEYN(E93Mg~R(x&{`yhNdBg zhE@iqRwgFe29{O^2Ep~ezoBTz%}>cptHiBANy*s@s6i5BLvVgtNqJ&XDnogBxn5>o dc5!lIL8@MUQTpt6Hc~)E44$rjF6*2UngGH^lr8`O literal 0 HcmV?d00001 diff --git a/addon/operators.py b/addon/operators.py new file mode 100644 index 0000000..2eb1987 --- /dev/null +++ b/addon/operators.py @@ -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 . +''' + +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() diff --git a/addon/ui.py b/addon/ui.py new file mode 100644 index 0000000..545cb05 --- /dev/null +++ b/addon/ui.py @@ -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 . +''' + +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) diff --git a/addon/ui_renderer.py b/addon/ui_renderer.py new file mode 100644 index 0000000..7d009de --- /dev/null +++ b/addon/ui_renderer.py @@ -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 . +''' + +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') diff --git a/addon/utils.py b/addon/utils.py new file mode 100644 index 0000000..0110156 --- /dev/null +++ b/addon/utils.py @@ -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 . +''' + +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() diff --git a/src/math_util.cpp b/src/math_util.cpp new file mode 100644 index 0000000..ebfa25c --- /dev/null +++ b/src/math_util.cpp @@ -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 . +*/ + +#include "math_util.h" + +#include +#include + +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; +} diff --git a/src/math_util.h b/src/math_util.h new file mode 100644 index 0000000..305f109 --- /dev/null +++ b/src/math_util.h @@ -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 . +*/ + +#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_ diff --git a/src/symtex_processor.cpp b/src/symtex_processor.cpp new file mode 100644 index 0000000..5b6e71b --- /dev/null +++ b/src/symtex_processor.cpp @@ -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 . +*/ + +#include "symtex_processor.h" + +#include +#include +#include +#include + +#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::max(); + minUvCoord[1] = numeric_limits::max(); + maxUvCoord[0] = -numeric_limits::max(); + maxUvCoord[1] = -numeric_limits::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; +} diff --git a/src/symtex_processor.h b/src/symtex_processor.h new file mode 100644 index 0000000..a409541 --- /dev/null +++ b/src/symtex_processor.h @@ -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 . +*/ + +#ifndef SYMTEX_PROCESSOR_H_ +#define SYMTEX_PROCESSOR_H_ + +#include + +#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 m_vertexCoords; + std::vector m_vertexNormals; + std::vector m_screenCoords; + std::vector m_vertexIndices; + std::vector m_uvCoords; + std::vector m_triangles; + std::vector m_vertexStates; + int m_imageWidth; + int m_imageHeight; + float *m_imagePixels; + float *m_originalImagePixels; + unsigned short *m_imageAlpha; + std::vector m_pixelStates; + int m_mirrorAxis; + float m_brushSize; + float m_brushStrength; + int m_brushFalloffType; +}; + +#endif // SYMTEX_PROCESSOR_H_