From e71821fb792004d8a79199111e1583170364edaf Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Wed, 3 May 2023 17:47:37 -0500 Subject: [PATCH] progress --- exam-2/9a.jpg | Bin 0 -> 17678 bytes exam-2/exam2.md | 307 +++++++++++++++++++++++++++++++++++++----------- exam-2/exam2.py | 170 +++++++++++++++++++++++++-- flake.nix | 3 +- 4 files changed, 397 insertions(+), 83 deletions(-) create mode 100644 exam-2/9a.jpg diff --git a/exam-2/9a.jpg b/exam-2/9a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..69ec6ce2922732e17570743d9a888494ced2f449 GIT binary patch literal 17678 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 0 + ``` + + I then plotted the various numbers to see if they match: + + ![](9a.jpg){width=40%} + + The 3 digit number corresponds to 1 if it's "inside" and 0 if it's not + "inside" for each of the 3 edges. The first digit corresponds to the top + horizontal edge, the second digit corresponds to the right most edge, and the + last digit corresponds to the long diagonal. When all three are 1, the pixel + is officially "inside" the triangle for sure. + + There is also edge detection to see if the edge pixels belong to the left or + the top edges. I didn't implement that here but I talk about it below in the + second part b. + b. \c{(3 points) Consider the three pixels $p_4 = (6, 4), p_5 = (7, 7)$, and $p_6 = (10, 8)$. Which of these would be considered to lie inside the triangle, according to the methods taught in class?} + For these three pixels, we can start with $p_4$ and define $a$ and $b$ using + it (going to $p_6$ first to remain in counter-clockwise order). + + Then we use the checks to determine if the $a$ and $b$ values satisfy the + conditions for being left or top edges: + + ```py + p4 = (6, 4) + p5 = (7, 7) + p6 = (10, 8) + + for (i, (p0_, p1_)) in enumerate([(p4, p6), (p6, p5), (p5, p4)]): + a= -(p1_[1] - p0_[1]) + b = (p1_[0] - p0_[0]) + c = (p1_[1] - p0_[1]) * p0_[0] - (p1_[0] - p0_[0]) * p0_[1] + + print(a, b, c, end=" ") + + if a == 0 and b < 0: print("top") + elif a > 0: print("left") + else: print() + ``` + + This tells us that the $p_6 \rightarrow p_5$ and the $p_5 \rightarrow p_4$ + edges are both left edges. If you graph this on the grid, this is accurate. + This means for those particular edges, the points that lie exactly on the + edge will be considered "inside" and for others, it will not. + + Edge detection can be done by subtracting the point from the normal and + seeing if the resulting vector is normal or not. + 10. \c{When a model contains many triangles that form a smoothly curving surface patch, it can be inefficient to separately represent each triangle in the patch independently as a set of three vertices because memory is wasted when diff --git a/exam-2/exam2.py b/exam-2/exam2.py index cf29637..4ffd700 100644 --- a/exam-2/exam2.py +++ b/exam-2/exam2.py @@ -1,14 +1,37 @@ import itertools import numpy as np import math -from sympy import N, Number, Rational, init_printing, latex, simplify, Expr +from sympy import N, Matrix, Number, Rational, init_printing, latex, simplify, Expr from sympy.vector import CoordSys3D, Vector +from PIL import Image, ImageDraw init_printing() import sympy +C = CoordSys3D('C') unit = lambda v: v/np.linalg.norm(v) +vec = lambda a, b, c: a * C.i + b * C.j + c * C.k + +def ap(matrix, vector): + vector_ = np.r_[vector, [1]] + trans_ = matrix @ vector_ + trans = trans_[:3] + return trans + +def ap2(matrix, vector): + c = vector.components + vector_ = vector.to_matrix(C).col_join(Matrix([[1]])) + trans_ = matrix @ vector_ + return trans_[0] * C.i + trans_[1] * C.j + trans_[2] * C.k + +def pv(vector): + c = vector.components + x = c.get(C.i, 0) + y = c.get(C.j, 0) + z = c.get(C.k, 0) + return (x, y, z) + def perspective_matrix(vfov, width, height, left, right, bottom, top, near, far): aspect = width / height @@ -62,7 +85,6 @@ def print_bmatrix(arr): print("\\\\") def problem_1(): - C = CoordSys3D('C') p = 1 * C.i + 4 * C.j + 8 * C.k e = 0 * C.i + 0 * C.j + 0 * C.k s = 2 * C.i + 2 * C.j + 10 * C.k @@ -302,10 +324,9 @@ def problem_6(): near = min(map(lambda p: p[2], points)) far = max(map(lambda p: p[2], points)) + M_this = M(left, right, bottom, top, near, far) for point in points: - point_ = np.r_[point, [1]] - trans_ = M(left, right, bottom, top, near, far) @ point_ - trans = trans_[:3] + trans = ap(M_this, point) v = np.vectorize(lambda x: float(x.evalf())) l = np.vectorize(lambda x: latex(simplify(x))) point = l(point) @@ -330,12 +351,139 @@ def problem_6(): calculate(oblique_transform, points) def problem_9(): + p0 = (3, 3) + p1 = (9, 5) + p2 = (11, 11) + + statuses = {} + + for (i, (p0_, p1_)) in enumerate([(p0, p1), (p1, p2), (p2, p0)]): + a= -(p1_[1] - p0_[1]) + b = (p1_[0] - p0_[0]) + c = (p1_[1] - p0_[1]) * p0_[0] - (p1_[0] - p0_[0]) * p0_[1] + + for x, y in itertools.product(range(3, 12), range(3, 12)): + if (x, y) not in statuses: statuses[x, y] = [None, None, None] + e = a * x + b * y + c + statuses[x, y][i] = e >= 0 + + CELL_SIZE = 30 + im = Image.new("RGB", (9 * CELL_SIZE, 9 * CELL_SIZE)) + draw = ImageDraw.Draw(im) + in_color = (180, 255, 180) + out_color = (255, 180, 180) + for (x, y), status in statuses.items(): + color = in_color if all(status) else out_color + sx, sy = x - 3, y - 3 + ex, ey = sx + 1, sy + 1 + draw.rectangle([ + (sx * CELL_SIZE, sy * CELL_SIZE), + (ex * CELL_SIZE, ey * CELL_SIZE), + ], color) + text = "".join(map(lambda s: "1" if s else "0", status)) + _, _, w, h = draw.textbbox((0, 0), text) + draw.text( + (sx * CELL_SIZE + (CELL_SIZE - w) / 2.0, sy * CELL_SIZE + (CELL_SIZE - h) / 2.0), + text, + "black" + ) + + im.save("9a.jpg") + + p4 = (6, 4) + p5 = (7, 7) + p6 = (10, 8) + + for (i, (p0_, p1_)) in enumerate([(p4, p6), (p6, p5), (p5, p4)]): + a= -(p1_[1] - p0_[1]) + b = (p1_[0] - p0_[0]) + c = (p1_[1] - p0_[1]) * p0_[0] - (p1_[0] - p0_[0]) * p0_[1] + + print(a, b, c, end=" ") + + if a == 0 and b < 0: print("top") + elif a > 0: print("left") + else: print() + pass -print("\nPROBLEM 8 -------------------------"); problem_8() -print("\nPROBLEM 5 -------------------------"); problem_5() +def problem_2(): + y_axis_angle = 3 * sympy.pi / 4 + + cos_t = -2 + sin_t = 2 + step_1 = Matrix([ + [cos_t, 0, sin_t, 0], + [0, 1, 0, 0], + [-sin_t, 0, cos_t, 0], + [0, 0, 0, 1], + ]) + + sqrt2 = sympy.sqrt(2) + cos_t = 2 * sqrt2 + sin_t = 1 + step_2 = Matrix([ + [1, 0, 0, 0], + [0, cos_t, -sin_t, 0], + [0, sin_t, cos_t, 0], + [0, 0, 0, 1], + ]) + + up_dir = vec(0, 1, 0) + nose_dir = vec(0, 0, 1) + left_wing_dir = vec(1, 0, 0) + right_wing_dir = vec(+1, 0, 0) + + def apply(m): + print("- nose (z):", pv(ap2(m, nose_dir))) + print("- up (y):", pv(ap2(m, up_dir))) + print("- leftwing (+x):", pv(ap2(m, left_wing_dir))) + print("- rightwing (-x):", pv(ap2(m, right_wing_dir))) + + print("step 1") + apply(step_1) + print() + + print("step 2") + apply(step_2) + print() + + print("step 2 @ step 1") + apply(step_2 @ step_1) + print() + + print("step 1 @ step 2") + apply(step_1 @ step_2) + print() + + print("SHIET") + print(vec(2, 1, -2).cross(vec(-2, 2, -1))) + R = Matrix([ + [-2, 2, -1, 0], + [1, 2, 2, 0], + [2, 1, -2, 0], + [0, 0, 0, 1], + ]).transpose() + print(R) + apply(R) + print() + + T = Matrix([ + [1, 0, 0, 4], + [0, 1, 0, 4], + [0, 0, 1, 7], + [0, 0, 0, 1], + ]) + + print("T @ R") + print(T @ R) + +# print("\nPROBLEM 8 -------------------------"); problem_8() +# print("\nPROBLEM 5 -------------------------"); problem_5() +# print("\nPROBLEM 9 -------------------------"); problem_9() +# print("\nPROBLEM 7 -------------------------"); problem_7() +# print("\nPROBLEM 4 -------------------------"); problem_4() +# print("\nPROBLEM 6 -------------------------"); problem_6() +# print("\nPROBLEM 1 -------------------------"); problem_1() +print("\nPROBLEM 2 -------------------------"); problem_2() print("\nPROBLEM 9 -------------------------"); problem_9() -print("\nPROBLEM 7 -------------------------"); problem_7() -print("\nPROBLEM 4 -------------------------"); problem_4() -print("\nPROBLEM 6 -------------------------"); problem_6() -print("\nPROBLEM 1 -------------------------"); problem_1() diff --git a/flake.nix b/flake.nix index fb29e88..18565d1 100644 --- a/flake.nix +++ b/flake.nix @@ -43,7 +43,8 @@ zip zathura - (python310.withPackages (p: with p; [ ipython numpy scipy sympy ])) + (python310.withPackages + (p: with p; [ ipython numpy scipy sympy pillow ])) ]) ++ (with toolchain; [ cargo rustc