''' Copyright (C) 2023 CG Cookie http://cgcookie.com hello@cgcookie.com Created by Jonathan Denning, Jonathan Williamson 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 mathutils import Matrix, Vector from bpy_extras.view3d_utils import ( location_3d_to_region_2d, region_2d_to_vector_3d, region_2d_to_location_3d, region_2d_to_origin_3d, ) from ...config.options import options from ...addon_common.common.debug import dprint from ...addon_common.common.profiler import profiler from ...addon_common.common.maths import Point, Vec, Direction, Normal from ...addon_common.common.maths import Ray, XForm, Plane from ...addon_common.common.maths import Point2D, Vec2D, Direction2D from ...addon_common.common.decorators import blender_version_wrapper class RetopoFlow_Spaces: ''' converts entities between screen space and world space Note: if 2D is not specified, then it is a 1D or 3D entity (whichever is applicable) ''' def update_clip_settings(self, *, rescale=True): if options['clip auto adjust']: # adjust clipping settings view_origin = self.drawing.get_view_origin(orthographic_distance=1000) view_focus = self.actions.r3d.view_location bbox = self.sources_bbox closest = bbox.closest_Point(view_origin) farthest = bbox.farthest_Point(view_origin) self.drawing.space.clip_start = max( options['clip auto start min'], (view_origin - closest).length * options['clip auto start mult'], ) self.drawing.space.clip_end = min( options['clip auto end max'], (view_origin - farthest).length * options['clip auto end mult'], ) # print(f'clip auto adjusting') # print(f' origin: {view_origin}') # print(f' focus: {view_focus}') # print(f' closest: {closest}') # print(f' farthest: {farthest}') # print(f' dist from origin to closest: {(view_origin - closest).length}') # print(f' dist from origin to farthest: {(view_origin - farthest).length}') # print(f' dist from origin to focus: {(view_origin - view_focus).length}') # print(f' clip_start: {self.drawing.space.clip_start}') # print(f' clip_end: {self.drawing.space.clip_end}') elif rescale: self.end_normalize(self.context) self.start_normalize() # self.unscale_from_unit_box() # self.scale_to_unit_box( # clip_override=options['clip override'], # clip_start=options['clip start override'], # clip_end=options['clip end override'], # ) def get_view_origin(self): # does not work in ORTHO view_loc = self.actions.r3d.view_location view_dist = self.actions.r3d.view_distance view_rot = self.actions.r3d.view_rotation view_cam = Point(view_loc + (view_rot @ Vector((0,0,view_dist)))) return view_cam def get_view_direction(self): view_rot = self.actions.r3d.view_rotation return Direction(view_rot @ Vector((0, 0, -1))) def Point2D_to_Vec(self, xy:Point2D): if xy is None: return None v = region_2d_to_vector_3d(self.actions.region, self.actions.r3d, xy) if v is None: return None return Vec(v) def Point2D_to_Direction(self, xy:Point2D): if xy is None: return None d = region_2d_to_vector_3d(self.actions.region, self.actions.r3d, xy) if d is None: return None return Direction(d) def Point2D_to_Origin(self, xy:Point2D): if xy is None: return None o = region_2d_to_origin_3d(self.actions.region, self.actions.r3d, xy) if o is None: return None return Point(o) def Point2D_to_Ray(self, xy:Point2D, *, min_dist=0.0): if xy is None: return None o, d = self.Point2D_to_Origin(xy), self.Point2D_to_Direction(xy) if o is None or d is None: return None return Ray(o, d, min_dist=min_dist) def Point2D_to_Point(self, xy:Point2D, depth:float): r = self.Point2D_to_Ray(xy) if r is None or r.o is None or r.d is None or depth is None: # dprint(r) pass # dprint(depth) pass return None return r.eval(depth) # Point(r.o + depth * r.d) #return Point(region_2d_to_location_3d(self.actions.region, self.actions.r3d, xy, depth)) def Point2D_to_Plane(self, xy0:Point2D, xy1:Point2D): ray0,ray1 = self.Point2D_to_Ray(xy0),self.Point2D_to_Ray(xy1) o = ray0.o + ray0.d n = Normal((ray1.o + ray1.d - o).cross(ray0.d)) return Plane(o, n) def Point_to_Point2D(self, xyz:Point): if not xyz: return None xy = location_3d_to_region_2d(self.actions.region, self.actions.r3d, xyz) if xy is None: return None return Point2D(xy) alerted_small_clip_start = False def Point_to_depth(self, xyz): ''' computes the distance of point (xyz) from view camera ''' if not xyz: return None xy = self.Point_to_Point2D(xyz) if xy is None: return None oxyz = self.Point2D_to_Origin(xy) return (xyz - oxyz).length def Point_to_Direction(self, xyz:Point): if not xyz: return None xy = location_3d_to_region_2d(self.actions.region, self.actions.r3d, xyz) return self.Point2D_to_Direction(xy) # @profiler.function def Point_to_Ray(self, xyz:Point, min_dist=0, max_dist_offset=0): if not xyz: return None xy = location_3d_to_region_2d(self.actions.region, self.actions.r3d, xyz) if not xy: return None o = self.Point2D_to_Origin(xy) #return Ray.from_segment(o, xyz) d = self.Point2D_to_Vec(xy) if o is None or d is None: return None dist = (o - xyz).length return Ray(o, d, min_dist=min_dist, max_dist=dist+max_dist_offset) def size2D_to_size(self, size2D:float, depth:float): # computes size of 3D object at distance (depth) as it projects to 2D size # TODO: there are more efficient methods of computing this! # find center of screen xy = Vec2D((self.actions.region.width, self.actions.region.height)) * 0.5 # note: scaling then unscaling helps with numerical instability when clip_start is small scale = 1000.0 p3d0 = self.Point2D_to_Point(xy, depth) p3d1 = self.Point2D_to_Point(xy + Vec2D((0, scale * size2D)), depth) if not p3d0 or not p3d1: return None return (p3d0 - p3d1).length / scale def size_to_size2D(self, size:float, xyz:Point): if not xyz: return None xy = self.Point_to_Point2D(xyz) if not xy: return None pt2D = self.Point_to_Point2D(xyz - self.Vec_up() * size) if not pt2D: return None return abs(xy.y - pt2D.y) def Point2D_in_area(self, p2D): return p2D and (0 <= p2D.x <= self.actions.size.x) and (0 <= p2D.y <= self.actions.size.y) ############################################# # return camera up and right vectors def Vec_up(self): # TODO: remove invert! return self.actions.r3d.view_matrix.to_3x3().inverted_safe() @ Vector((0,1,0)) def Vec_right(self): # TODO: remove invert! return self.actions.r3d.view_matrix.to_3x3().inverted_safe() @ Vector((1,0,0)) def Vec_forward(self): # TODO: remove invert! return self.actions.r3d.view_matrix.to_3x3().inverted_safe() @ Vector((0,0,-1))