def unit_normal(a, b, c): mat_x = Matrix(((1, a[1], a[2]), (1, b[1], b[2]), (1, c[1], c[2]))) mat_y = Matrix(((a[0], 1, a[2]), (b[0], 1, b[2]), (c[0], 1, c[2]))) mat_z = Matrix(((a[0], a[1], 1), (b[0], b[1], 1), (c[0], c[1], 1))) x = Matrix.determinant(mat_x) y = Matrix.determinant(mat_y) z = Matrix.determinant(mat_z) magnitude = (x**2 + y**2 + z**2)**.5 return (x/magnitude, y/magnitude, z/magnitude)
def unit_normal(a, b, c): mat_x = Matrix(((1, a[1], a[2]), (1, b[1], b[2]), (1, c[1], c[2]))) mat_y = Matrix(((a[0], 1, a[2]), (b[0], 1, b[2]), (c[0], 1, c[2]))) mat_z = Matrix(((a[0], a[1], 1), (b[0], b[1], 1), (c[0], c[1], 1))) x = Matrix.determinant(mat_x) y = Matrix.determinant(mat_y) z = Matrix.determinant(mat_z) magnitude = (x**2 + y**2 + z**2)**.5 return (x / magnitude, y / magnitude, z / magnitude)
def pointInTri2D(v, v1, v2, v3): global dict_matrix key = v1.x, v1.y, v2.x, v2.y, v3.x, v3.y # Commented because its slower to do teh bounds check, we should realy cache the bounds info for each face. ''' # BOUNDS CHECK xmin= 1000000 ymin= 1000000 xmax= -1000000 ymax= -1000000 for i in (0,2,4): x= key[i] y= key[i+1] if xmax<x: xmax= x if ymax<y: ymax= y if xmin>x: xmin= x if ymin>y: ymin= y x= v.x y= v.y if x<xmin or x>xmax or y < ymin or y > ymax: return False # Done with bounds check ''' try: mtx = dict_matrix[key] if not mtx: return False except: side1 = v2 - v1 side2 = v3 - v1 nor = side1.cross(side2) mtx = Matrix((side1, side2, nor)) # Zero area 2d tri, even tho we throw away zerop area faces # the projection UV can result in a zero area UV. if not mtx.determinant(): dict_matrix[key] = None return False mtx.invert() dict_matrix[key] = mtx uvw = (v - v1) * mtx return 0 <= uvw[0] and 0 <= uvw[1] and uvw[0] + uvw[1] <= 1
def physics_mass_center(mesh): '''Calculate (final triangulated) mesh's mass and center of mass based on volume''' volume = 0. mass = 0. com = Vector() # Based on Stan Melax's volint for face in mesh.polygons: a = Matrix((mesh.vertices[face.vertices[0]].co, mesh.vertices[face.vertices[1]].co, mesh.vertices[face.vertices[2]].co)) vol = a.determinant() volume += vol com += vol * (a[0] + a[1] + a[2]) if volume > 0: com /= volume * 4. mass = volume / 6. return mass, com else: return mass, Vector()
def loadStatueMinusPose(context): ob,statue,scn = getMeshes(context) ob,rig,posed = applyArmature(context) posed.name = "Temporary" nVerts = len(ob.data.vertices) relMats = {} for vg in ob.vertex_groups: try: pb = rig.pose.bones[vg.name] except KeyError: pb = None if pb: relMats[vg.index] = pb.matrix * pb.bone.matrix_local.inverted() else: print("Skipping vertexgroup %s" % vg.name) relMats[vg.index] = Matrix().identity() svs = statue.data.vertices pvs = posed.data.vertices ovs = ob.data.vertices skey = createNewMeshShape(ob, statue.name, scn) relmat = Matrix() y = Vector((0,0,0,1)) for v in ob.data.vertices: vn = v.index diff = svs[vn].co - pvs[vn].co if diff.length > 1e-4: relmat.zero() wsum = 0.0 for g in v.groups: w = g.weight relmat += w * relMats[g.group] wsum += w factor = 1.0/wsum relmat *= factor y[:3] = svs[vn].co x = relmat.inverted() * y skey.data[vn].co = Vector(x[:3]) z = relmat * x xdiff = skey.data[vn].co - ovs[vn].co if False and vn in [8059]: print("\nVert", vn, diff.length, xdiff.length) print("det", relmat.determinant()) print("d (%.4f %.4f %.4f)" % tuple(diff)) print("xd (%.4f %.4f %.4f)" % tuple(xdiff)) checkRotationMatrix(relmat) print("Rel", relmat) print("Inv", relmat.inverted()) s = pvs[vn].co print("s ( %.4f %.4f %.4f)" % (s[0],s[1],s[2])) print("x ( %.4f %.4f %.4f)" % (x[0],x[1],x[2])) print("y ( %.4f %.4f %.4f)" % (y[0],y[1],y[2])) print("z ( %.4f %.4f %.4f)" % (z[0],z[1],z[2])) o = ovs[vn].co print("o (%.4f %.4f %.4f)" % (o[0],o[1],o[2])) print("r (%.4f %.4f %.4f)" % tuple(skey.data[vn].co)) for g in v.groups: print("\nGrp %d %f %f" % (g.group, g.weight, relMats[g.group].determinant())) print("Rel", relMats[g.group]) #halt #scn.objects.unlink(statue) scn.objects.unlink(posed)
def generate_bezier( points: List[Vector], # puntos first: int, # idx primer punto last: int, # idx último punto parametro: List[float], # parámetro that_1: Vector, # tangente al primer punto that_2: Vector ) -> List[Vector]: # tangente al último punto n_pts = last - first + 1 a = np.ndarray([n_pts, 2], dtype=Vector) C = Matrix([[0, 0], [0, 0]]) X = Vector((0, 0)) tmp: Vector bez_curve = [Vector((0, 0, 0)) for _ in range(4)] # Compute the A's for i in range(n_pts): a[i][0] = that_1 * b_1(parametro[i]) a[i][1] = that_2 * b_2(parametro[i]) P_0 = points[first] P_3 = points[last] # Create the C and X matrices for i in range(n_pts): C.row[0][0] += a[i][0].length_squared C.row[0][1] += a[i][0].dot(a[i][1]) C.row[1][0] = C.row[0][1] C.row[1][1] = a[i][1].length_squared tmp = (points[first + i] - ((P_0 * b_0(parametro[i])) + ((P_0 * b_1(parametro[i])) + ((P_3 * b_2(parametro[i])) + (P_3 * b_3(parametro[i])))))) X[0] += a[i, 0].dot(tmp) X[1] += a[i, 1].dot(tmp) # Compute the determinants of C and X # C.row[0][0] * C.row[1][1] - C.row[1][0]*C.row[0][1] det_C0_C1 = C.determinant() det_C0_X = C.row[0][0] * X[1] - C.row[1][0] * X[0] det_X_C1 = X[0] * C.row[1][1] - X[1] * C.row[0][1] # Finally, derive alpha values alpha_l = 0.0 if det_C0_C1 == 0 else det_X_C1 / det_C0_C1 alpha_r = 0.0 if det_C0_C1 == 0 else det_C0_X / det_C0_C1 # If alpha negative, use the Wu/Barsky heuristic (see text) # (if alpha is 0, you get coincident control points that lead to # divide by zero in any subsequent NewtonRaphsonRootFind() call. segLength = (points[first] - points[last]).length epsilon = 1.0e-6 * segLength if alpha_l < epsilon or alpha_r < epsilon: # fall back on standard(probably inaccurate) formula, # and subdivide further if needed. dist = segLength / 3.0 bez_curve[0] = points[first] bez_curve[3] = points[last] bez_curve[1] = (that_1 * dist) + bez_curve[0] bez_curve[2] = (that_2 * dist) + bez_curve[3] return bez_curve # First and last control points of the Bezier curve are # positioned at the first and last data points # Control points 1 and 2 are positioned an alpha distance out # on the tangent vectors, left and right, respectively bez_curve[0] = points[first] bez_curve[3] = points[last] bez_curve[1] = (that_1 * alpha_l) + bez_curve[0] bez_curve[2] = (that_2 * alpha_r) + bez_curve[3] return bez_curve
class RipConversion(object): def __init__(self): self.matrix = Matrix().to_3x3() self.flip_winding = False self.use_normals = True self.use_weights = True self.filter_unused_attrs = True self.filter_unused_textures = True self.normal_max_int = 255 self.normal_scale = [(1.0, 0.0)] * 3 self.uv_max_int = 255 self.uv_scale = [(1.0, 0.0)] * 2 self.filter_duplicates = False self.attr_override_table = None self.dedup = BaseDuplicateTracker() def find_attrs(self, rip, semantic): if self.attr_override_table: if semantic in self.attr_override_table: return [rip.attributes[i] for i in self.attr_override_table[semantic] if i < len(rip.attributes)] else: return [] else: return rip.find_attrs(semantic) def find_attrs_used(self, rip, semantic, filter=True): attrs = self.find_attrs(rip, semantic) if rip.shader_vert and filter: return [attr for attr in attrs if rip.is_used_attr(attr)] return attrs def scale_normal(self, comp, val): return val * self.normal_scale[comp][0] + self.normal_scale[comp][1] def convert_normal(self, rip, vec_id, norm): return (self.scale_normal(0,norm[0]), self.scale_normal(1,norm[1]), self.scale_normal(2,norm[2])) def find_normals(self, rip): return self.find_attrs_used(rip, 'NORMAL', self.filter_unused_attrs) def get_normals(self, rip): normals = self.find_normals(rip) if len(normals) == 0: return None normdata = normals[0].as_floats(3, self.normal_max_int) for i in range(len(normdata)): normdata[i] = self.convert_normal(rip, i, normdata[i]) return normdata def scale_uv(self, comp, val): return val * self.uv_scale[comp][0] + self.uv_scale[comp][1] def convert_uv(self, rip, vec_id, uv): return (self.scale_uv(0,uv[0]), self.scale_uv(1,uv[1])) def find_uv_maps(self, rip): return self.find_attrs_used(rip, 'TEXCOORD', self.filter_unused_attrs) def get_uv_maps(self, rip): maps = self.find_uv_maps(rip) if len(maps) == 0: return [] # Output each pair of UV values as a map all_uvs = concat_attrs(list(map(lambda attr: attr.as_floats(4, self.uv_max_int), maps))) count = int((len(all_uvs[0])+1)/2) result_maps = [] for i in range(count): result_maps.append([]) for i in range(rip.num_verts): data = all_uvs[i] for j in range(count): pair = data[2*j:2*j+2] if len(pair) == 1: pair = (pair[0], 0.0) result_maps[j].append(self.convert_uv(rip, i, pair)) return result_maps def find_colors(self, rip): return self.find_attrs_used(rip, 'COLOR', self.filter_unused_attrs) def get_weight_groups(self, rip): indices = self.find_attrs_used(rip, 'BLENDINDICES', self.filter_unused_attrs) weights = self.find_attrs_used(rip, 'BLENDWEIGHT', self.filter_unused_attrs) if len(indices) == 0 or len(weights) == 0: return {} all_indices = concat_attrs(list(map(lambda attr: attr.data, indices))) all_weights = concat_attrs(list(map(lambda attr: attr.as_floats(), weights))) count = min(len(all_indices[0]), len(all_weights[0])) groups = {} for i in range(rip.num_verts): for j in range(count): idx = all_indices[i][j] weight = all_weights[i][j] if weight != 0: if idx not in groups: groups[idx] = {} groups[idx][i] = weight return groups def apply_matrix(self, vec): return self.matrix * Vector(vec).to_3d() def apply_matrix_list(self, lst): return list(map(self.apply_matrix, lst)) def convert_mesh(self, rip): pos_attrs = self.find_attrs(rip, 'POSITION') if len(pos_attrs) == 0: pos_attrs = rip.attributes[0:1] vert_pos = self.apply_matrix_list(pos_attrs[0].as_floats(3)) # Rewind triangles when necessary faces = rip.faces if (self.matrix.determinant() < 0) != self.flip_winding: faces = list(map(lambda f: (f[1],f[0],f[2]), faces)) # Create mesh mesh = bpy.data.meshes.new(rip.basename) mesh.from_pydata(vert_pos, [], faces) # Assign normals mesh.polygons.foreach_set("use_smooth", [True] * len(faces)) if self.use_normals: normals = self.get_normals(rip) if normals is not None: mesh.use_auto_smooth = True mesh.show_normal_vertex = True mesh.show_normal_loop = True mesh.normals_split_custom_set_from_vertices(self.apply_matrix_list(normals)) mesh.update() # Switch to bmesh bm = bmesh.new() vgroup_names = [] try: bm.from_mesh(mesh) bm.verts.ensure_lookup_table() # Create UV maps uv_maps = self.get_uv_maps(rip) for idx,uvdata in enumerate(uv_maps): layer = bm.loops.layers.uv.new('uv'+str(idx)) for i,vert in enumerate(bm.verts): uv = mathutils.Vector(uvdata[i]) for loop in vert.link_loops: loop[layer].uv = uv # Create color maps colors = self.find_colors(rip) def add_color_layer(name,cdata): layer = bm.loops.layers.color.new(name) for i,vert in enumerate(bm.verts): color = mathutils.Vector(cdata[i]) for loop in vert.link_loops: loop[layer] = color for idx,cattr in enumerate(colors): if cattr.items < 3: continue cdata = cattr.as_floats(4, 255) add_color_layer('color'+str(idx), list(map(lambda v: v[0:3], cdata))) if cattr.items == 4: add_color_layer('alpha'+str(idx), list(map(lambda v: (v[3],v[3],v[3]), cdata))) # Create weight groups if self.use_weights: groups = self.get_weight_groups(rip) for group in sorted(groups.keys()): id = len(vgroup_names) vgroup_names.append(str(group)) layer = bm.verts.layers.deform.verify() weights = groups[group] for vid in weights.keys(): bm.verts[vid][layer][id] = weights[vid] bm.to_mesh(mesh) finally: bm.free() # Finalize mesh.update() if self.dedup.is_sharing_mesh(): mesh.materials.append(None) if len(vgroup_names) > 0: mesh["ninjarip_vgroups"] = ','.join(vgroup_names); return mesh def mesh_datakey(self, rip): key = rip.data_hash if self.filter_unused_attrs and rip.shader_vert: indices = [str(i) for i in range(len(rip.attributes)) if rip.is_used_attr(rip.attributes[i])] key = key + ':' + ','.join(indices) return key def create_object(self, rip, obj_name, mesh, mat): nobj = bpy.data.objects.new(obj_name, mesh) if mat: if self.dedup.is_sharing_mesh(): nobj.material_slots[0].link = 'OBJECT' nobj.material_slots[0].material = mat else: mesh.materials.append(mat) if 'ninjarip_vgroups' in mesh: for vname in mesh["ninjarip_vgroups"].split(','): nobj.vertex_groups.new('blendweight'+vname) for i in range(len(rip.shaders)): nobj["shader_"+str(i)] = rip.shaders[i] return nobj def convert_object(self, rip, scene, obj_name): mesh_key = self.mesh_datakey(rip) mesh = self.dedup.get_mesh(mesh_key, lambda: self.convert_mesh(rip)) # Textures texset = rip.get_textures(self.filter_unused_textures) mat = None matkey = '*' if len(texset) > 0: mat = self.dedup.get_material(rip, texset) matkey = mat['ninjarip_datakey'] # Create or find object objkey = '|'.join([mesh_key,matkey]) if self.filter_duplicates: nobj = self.dedup.get_object(objkey, lambda: self.create_object(rip, obj_name, mesh, mat)) else: nobj = self.create_object(rip, obj_name, mesh, mat) nobj['ninjarip_datakey'] = objkey # Select object found = False for o in scene.objects: o.select = False if o == nobj: found = True if not found: scene.objects.link(nobj) scene.update() nobj.select = True scene.objects.active = nobj return nobj
def texdata(self, face, mesh, obj): mat = None width = height = 64 if obj.material_slots: mat = obj.material_slots[face.material_index].material if mat: if mat.node_tree: for node in mat.node_tree.nodes: if node.type == 'TEX_IMAGE': if node.image.has_data: width, height = node.image.size break texstring = mat.name.replace(" ", "_") else: texstring = self.option_skip V = [loop.vert.co for loop in face.loops] uv_layer = mesh.loops.layers.uv.active if uv_layer is None: uv_layer = mesh.loops.layers.uv.new("dummy") T = [loop[uv_layer].uv for loop in face.loops] # UV handling ported from: https://bitbucket.org/khreathor/obj-2-map if self.option_format == 'Valve': # [ Ux Uy Uz Uoffs ] [ Vx Vy Vz Voffs ] rotation scaleU scaleV dummy = ' [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1\n' height = -height # workaround for flipped v # Set up "2d world" coordinate system with the 01 edge along X world01 = V[1] - V[0] world02 = V[2] - V[0] world01_02Angle = world01.angle(world02) if face.normal.dot(world01.cross(world02)) < 0: world01_02Angle = -world01_02Angle world01_2d = Vector((world01.length, 0.0)) world02_2d = Vector((math.cos(world01_02Angle), math.sin(world01_02Angle))) * world02.length # Get 01 and 02 vectors in UV space and scale them tex01 = T[1] - T[0] tex02 = T[2] - T[0] tex01.x *= width tex02.x *= width tex01.y *= height tex02.y *= height ''' a = world01_2d b = world02_2d p = tex01 q = tex02 [ px ] [ m11 m12 0 ] [ ax ] [ py ] = [ m21 m22 0 ] [ ay ] [ 1 ] [ 0 0 1 ] [ 1 ] [ qx ] [ m11 m12 0 ] [ bx ] [ qy ] = [ m21 m22 0 ] [ by ] [ 1 ] [ 0 0 1 ] [ 1 ] px = ax * m11 + ay * m12 py = ax * m21 + ay * m22 qx = bx * m11 + by * m12 qy = bx * m21 + by * m22 [ px ] [ ax ay 0 0 ] [ m11 ] [ py ] = [ 0 0 ax ay ] [ m12 ] [ qx ] [ bx by 0 0 ] [ m21 ] [ qy ] [ 0 0 bx by ] [ m22 ] ''' # Find an affine transformation to convert # world01_2d and world02_2d to their respective UV coords texCoordsVec = Vector((tex01.x, tex01.y, tex02.x, tex02.y)) world2DMatrix = Matrix(((world01_2d.x, world01_2d.y, 0, 0), (0, 0, world01_2d.x, world01_2d.y), (world02_2d.x, world02_2d.y, 0, 0), (0, 0, world02_2d.x, world02_2d.y))) try: mCoeffs = solve(world2DMatrix, texCoordsVec) except: return texstring + dummy right_2dworld = Vector(mCoeffs[0:2]) up_2dworld = Vector(mCoeffs[2:4]) # These are the final scale values # (avoid division by 0 for degenerate or missing UVs) scalex = 1 / max(0.00001, right_2dworld.length) scaley = 1 / max(0.00001, up_2dworld.length) scale = Vector((scalex, scaley)) # Get the angles of the texture axes. These are in the 2d world # coordinate system, so they're relative to the 01 vector right_2dworld_angle = math.atan2(right_2dworld.y, right_2dworld.x) up_2dworld_angle = math.atan2(up_2dworld.y, up_2dworld.x) # Recreate the texture axes in 3d world coordinates, # using the angles from the 01 edge rt = world01.normalized() up = rt.copy() rt.rotate(Matrix.Rotation(right_2dworld_angle, 3, face.normal)) up.rotate(Matrix.Rotation(up_2dworld_angle, 3, face.normal)) # Now we just need the offsets rt_full = rt.to_4d() up_full = up.to_4d() test_s = V[0].dot(rt) / (width * scale.x) test_t = V[0].dot(up) / (height * scale.y) rt_full[3] = (T[0].x - test_s) * width up_full[3] = (T[0].y - test_t) * height texstring += f" [ {self.printvec(rt_full)} ]"\ f" [ {self.printvec(up_full)} ]"\ f" 0 {self.printvec(scale)}\n" elif self.option_format == 'Quake': # offsetU offsetV rotation scaleU scaleV dummy = ' 0 0 0 1 1\n' # 01 and 02 in 3D space world01 = V[1] - V[0] world02 = V[2] - V[0] # 01 and 02 projected along the closest axis maxn = max(abs(round(crd, self.option_fp)) for crd in face.normal) for i in [2, 0, 1]: # axis priority for 45 degree angles if round(abs(face.normal[i]), self.option_fp) == maxn: axis = i break world01_2d = Vector((world01[:axis] + world01[(axis + 1):])) world02_2d = Vector((world02[:axis] + world02[(axis + 1):])) # 01 and 02 in UV space (scaled to texture size) tex01 = T[1] - T[0] tex02 = T[2] - T[0] tex01.x *= width tex02.x *= width tex01.y *= height tex02.y *= height # Find affine transformation between 2D and UV texCoordsVec = Vector((tex01.x, tex01.y, tex02.x, tex02.y)) world2DMatrix = Matrix(((world01_2d.x, world01_2d.y, 0, 0), (0, 0, world01_2d.x, world01_2d.y), (world02_2d.x, world02_2d.y, 0, 0), (0, 0, world02_2d.x, world02_2d.y))) try: mCoeffs = solve(world2DMatrix, texCoordsVec) except: return texstring + dummy # Build the transformation matrix and decompose it tformMtx = Matrix(((mCoeffs[0], mCoeffs[1], 0), (mCoeffs[2], mCoeffs[3], 0), (0, 0, 1))) rotation = math.degrees(tformMtx.inverted_safe().to_euler().z) scale = tformMtx.inverted_safe().to_scale() # never zero scale.x *= math.copysign(1, tformMtx.determinant()) # Calculate offsets t0 = Vector((T[0].x * width, T[0].y * height)) v0 = Vector((V[0][:axis] + V[0][(axis + 1):])) v0.rotate(Matrix.Rotation(math.radians(-rotation), 2)) v0 = Vector((v0.x / scale.x, v0.y / scale.y)) offset = t0 - v0 offset.y *= -1 # V is flipped finvals = [offset.x, offset.y, rotation, scale.x, scale.y] texstring += f" {self.printvec(finvals)}\n" return texstring