I have a task to sample a fixed number of points from a 3D cube, so that these points can be a point cloud. But I do not know 1) how to describe the surfaces of an arbitrary 3D cubes and 2) how to sample from that surface.
I can think of an approach that I first describe a unit cube with x = 1, x = -1, y = 1, y = -1, z = 1, z = -1, then sample from that cube first. Then I can scale and rotate every point. But then, what's the formula for rotating and scaling 3D objects? Is rotation for any single point just multiplying a 3-by-3 matrix? How about scaling?
I will also need to do these in a computational way so I can implement it in Python.
Any pointers to reference would be very helpful! I have not learnt geometry except for a course on vector calculus, so I have difficulty even find the correct term to Google my question. Thanks!
I am assuming you wish to sample points on the surface of a cuboid with vertices at $$\bbox{\begin{array}{l} \vec{v}_0 \\ \vec{v}_0 + \vec{v}_x \\ \vec{v}_0 + \vec{v}_y \\ \vec{v}_0 + \vec{v}_x + \vec{v}_y \\ \vec{v}_0 + \vec{v}_z \\ \vec{v}_0 + \vec{v}_x + \vec{v}_z \\ \vec{v}_0 + \vec{v}_y + \vec{v}_z \\ \vec{v}_0 + \vec{v}_x + \vec{v}_y + \vec{v}_z \\ \end{array}}$$ i.e. $\vec{v}_0$ is one of the vertices, and $\vec{v}_x$, $\vec{v}_y$, and $\vec{v}_z$ are the edges from that vertex to the three other vertices that vertex shares an edge with.
For the unit cube, you'd have $\vec{v}_0 = (0, 0, 0)$, $\vec{v}_x = (1, 0, 0)$, $\vec{v}_y = (0, 1, 0)$, and $\vec{v}_z = (0, 0, 1)$.
The cuboid has six faces. Each pair of opposing faces has the same area: $$\bbox{\begin{aligned} A_{x y} &= \left\lVert\vec{v}_x \times \vec{v}_y \right\rVert \\ A_{x z} &= \left\lVert\vec{v}_x \times \vec{v}_z \right\rVert \\ A_{y z} &= \left\lVert\vec{v}_y \times \vec{v}_z \right\rVert \\ \end{aligned}}$$ Note that if you have $\vec{a} = ( a_x , a_y , a_z )$ and $\vec{b} = ( b_x , b_y , b_z )$, $$\bbox{\vec{a} \times \vec{b} = ( a_y b_z - a_z b_y , a_z b_x - a_x b_z , a_x b_y - a_y b_x )}$$ and $$\bbox{\left\lVert\vec{a}\right\rVert = \sqrt{\vec{a} \cdot \vec{a}} = \sqrt{ a_x^2 + a_y^2 + a_z^2 }}$$
Generate two random scalars $0 \le i \le 1$ and $0 \le j \le 1$, and a random scalar $s$, $0 \le s \lt 2 A_{x y} + 2 A_{x z} + 2 A_{y z}$: $$\bbox{\begin{array}{l|c} \vec{p}(i, j) & s \\ \hline \vec{v}_0 + i \vec{v}_x + j \vec{v}_y & 0 \le s \lt A_{x y} \\ \vec{v}_0 + \vec{v}_z + i \vec{v}_x + j \vec{v}_y & A_{x y} \le s \lt 2 A_{x y} \\ \vec{v}_0 + i \vec{v}_x + v \vec{v}_z & 2 A_{x y} \le s \lt A_{x z} + 2 A_{x y} \\ \vec{v}_0 + \vec{v}_y + i \vec{v}_x + j \vec{v}_z & 2 A_{x y} + A_{x z} \le s \lt 2 A_{x z} + 2 A_{x y} \\ \vec{v}_0 + i \vec{v}_y + j \vec{v}_z & 2 A_{x y} + 2 A_{x z} \le s \lt A_{y z} + 2 A_{x z} + 2 A_{x y} \\ \vec{v}_0 + \vec{v}_x + i \vec{v}_y + j \vec{v}_z & 2 A_{x y} + 2 A_{x z} + A_{y z} \le s \lt 2 A_{y z} + 2 A_{x z} + 2 A_{x y} \\ \end{array}}$$ The uniform random point on the surface of the cuboid is then $\vec{p}(i, j)$, as shown in the left column corresponding to the range on the right side $s$ falls into.
The idea here is that we generate each point uniformly distributed within their face, and choose the face randomly weighted by their areas. This yields an uniform distribution on the face of the cuboid.
In Python:
Most of the code is the 3D real vector class,
Vec3d, implementation. As an example, you could do e.g.to print 1000 uniform random points on the surface of a cuboid with vertices at $(1.00, 2.00, 3.00)$, $(3.00, 6.00, 4.00)$, $(1.00, 1.00, 7.00)$, $(3.00, 5.00, 8.00)$, $(5.25, 0.00, 2.50)$, $(7.25, 4.00, 3.50)$, $(5.25, -1.00, 6.50)$, and $(7.25, 3.00, 7.50)$.
Sampling points using a regular grid on each face is simple, if you don't care whether the grid is orthogonal (or if the sides are all perpendicular to each other, $\vec{v}_x \cdot \vec{v}_y = \vec{v}_x \cdot \vec{v}_z = \vec{v}_y \cdot \vec{v}_z = 0$). In that case, just use a regular grid in $i$, $j$ on each face (separately), noting that the true length of the $i$ axis is $\left\lVert\vec{v}_x\right\rVert$ on the first four faces, and $\left\lVert\vec{v}_y\right\rVert$ on the last two faces; and the true length of the $j$ axis is $\left\lVert\vec{v}_y\right\rVert$ on the first two faces, and $\left\lVert\vec{v}_z\right\rVert$ on the last four faces.
Sampling points on a regular grid in Cartesian coordinates $x$, $y$, $z$ is much more interesting.
First, we consider the internal unit coordinate system $(i, j, k)$ within the cube, $0 \le i, j, k \le 1$: $$\bbox{\vec{p}(i, j, k) = \vec{v}_0 + i \vec{v}_x + j \vec{v}_y + k \vec{v}_z}$$ This means that point $\vec{p}(i, j, k) = (x, y, z)$ corresponds to coordinates $$\bbox{\begin{cases} d = \vec{v}_x \cdot ( \vec{v}_y \times \vec{v}_z ) \\ i = \frac{(\vec{p} - \vec{v}_0) \cdot ( \vec{v}_y \times \vec{v}_z )}{d} \\ j = \frac{(\vec{p} - \vec{v}_0) \cdot ( \vec{v}_z \times \vec{v}_x )}{d} \\ k = \frac{(\vec{p} - \vec{v}_0) \cdot ( \vec{v}_x \times \vec{v}_z )}{d} \\ \end{cases}}$$ Because the cuboid is a linear construct, we can use the above to solve the internal coordinates where any plane of our choosing (in the real 3D coordinates) intersects the cuboid.
If we use $\vec{n}$ for the normal of the plane we are interested in, and $d$ is the signed distance from origin to that plane in units of $\left\lVert\vec{n}\right\rVert$ (so point $\vec{p}$ is on the plane if $\vec{p} \cdot \vec{n} - d = 0$), we can write the equation for that plane in the cuboid internal coordinates $(i, j, k)$ as $$\bbox{ (\vec{n}\cdot\vec{v}_x) i + (\vec{n}\cdot\vec{v}_y) j + (\vec{n}\cdot\vec{v}_z) k + (\vec{n} \cdot \vec{v}_0 - d) = 0}$$
This plane intersects the twelve edges of our cuboid at $$\begin{array}{lll} \displaystyle i = \frac{d - \vec{n} \cdot \vec{v}_0 }{\vec{n} \cdot \vec{v}_x } , & j = 0 , & k = 0 \\ \displaystyle i = \frac{d - \vec{n} \cdot (\vec{v}_0 + \vec{v}_z) }{\vec{n} \cdot \vec{v}_x } , & j = 0 , & k = 1 \\ \displaystyle i = \frac{d - \vec{n} \cdot (\vec{v}_0 + \vec{v}_y) }{\vec{n} \cdot \vec{v}_x } , & j = 1 , & k = 0 \\ \displaystyle i = \frac{d - \vec{n} \cdot (\vec{v}_0 + \vec{v}_y + \vec{v}_z) }{\vec{n} \cdot \vec{v}_x } , & j = 1 , & k = 1 \\ \displaystyle j = \frac{d - \vec{n} \cdot \vec{v}_0 }{\vec{n} \cdot \vec{v}_y } , & i = 0 , & k = 0 \\ \displaystyle j = \frac{d - \vec{n} \cdot (\vec{v}_0 + \vec{v}_z) }{\vec{n} \cdot \vec{v}_y } , & i = 0 , & k = 1 \\ \displaystyle j = \frac{d - \vec{n} \cdot (\vec{v}_0 + \vec{v}_x) }{\vec{n} \cdot \vec{v}_y } , & i = 1 , & k = 0 \\ \displaystyle j = \frac{d - \vec{n} \cdot (\vec{v}_0 + \vec{v}_x + \vec{v}_z) }{\vec{n} \cdot \vec{v}_y } , & i = 1 , & k = 1 \\ \displaystyle k = \frac{d - \vec{n} \cdot \vec{v}_0}{\vec{n} \cdot \vec{v}_z } , & i = 0 , & j = 0 \\ \displaystyle k = \frac{d - \vec{n} \cdot (\vec{v}_0 + \vec{v}_x)}{\vec{n} \cdot \vec{v}_z } , & i = 1 , & j = 1\\ \displaystyle k = \frac{d - \vec{n} \cdot (\vec{v}_0 + \vec{v}_y)}{\vec{n} \cdot \vec{v}_z } , & i = 0 , & j = 0\\ \displaystyle k = \frac{d - \vec{n} \cdot (\vec{v}_0 + \vec{v}_x + \vec{v}_y)}{\vec{n} \cdot \vec{v}_z } , & i = 1 , & j = 1\\ \end{array}$$ If you intend to draw a 2D projection of the 3D cube, the above allows you to map any plane perpendicular to your view (i.e., a line in 2D) to one to six line segments in internal cuboid coordinates on the surface of the cuboid. (Intersection between a cube an a plane is a point, line, triangle, quadrilateral (or tetragon), pentagon, or hexagon.)
Or, in other words, the above allows you to map any 2D line in your orthographic 2D projection of the 3D cube to zero to six line segments on the surface of the cube.
(If you use a perspective projection, the sampling along each line does not occur at fixed intervals, if you wish the sampling to be regular in the 2D coordinate system.)
If you wish to sample the surface using lines parallel to the $z$ axis, i.e. find the point on the cuboid surface that corresponds to 3D coordinates $(x, y, \text{any})$, using $\vec{v}_0 = ( x_0 , y_0 , z_0 )$, $\vec{v}_x = ( x_1 , y_1 , z_1 )$, $\vec{v}_y = ( x_2 , y_2 , z_2 )$, and $\vec{v}_z = ( x_3 , y_3 , z_3 )$, you need to find the internal coordinates on each face of the cuboid: $$\bbox{\begin{array}{lll} i = \frac{(x - x_0) y_2 - (y - y_0) x_2}{x_1 y_2 - x_2 y_1} , & j = \frac{(y - y_0) x_1 - (x - x_0) y_1}{x_1 y_2 - x_2 y_1} , & k = 0 \\ i = \frac{(x - x_0 - x_3) y_2 - (y - y_0 - y_3) x_2}{x_1 y_2 - x_2 y_1 } , & j = \frac{(y - y_0 - y_3) x_1 - (x - x_0 - x_3) y_1}{x_1 y_2 - x_2 y_1 } , & k = 1 \\ i = \frac{(x - x_0) y_3 - (y - y_0) x_3}{x_1 y_3 - x_3 y_1} , & j = 0 , & k = \frac{(y - y_0) x_1 - (x - x_0) y_1}{x_1 y_3 - x_3 y_1} \\ i = \frac{(x - x_0 - x_2) y_3 - (y - y_0 - y_2) x_3}{x_1 y_3 - x_3 y_1 } , & j = 1 , & k = \frac{(y - y_0 - y_2) x_1 - (x - x_0 - x_2) y_1}{x_1 y_3 - x_3 y_1 } \\ i = 0 , & j = \frac{(x - x_0) y_3 - (y - y_0) x_3}{x_2 y_3 - x_3 y_2} , & k = \frac{(y - y_0) x_2 - (x - x_0) y_2}{x_2 y_3 - x_3 y_2} \\ i = 1 , & j = \frac{(x - x_0 - x_1) y_3 - (y - y_0 - y_1) x_3}{x_2 y_3 - x_3 y_2} , & k = \frac{(y - y_0 - y_1) x_2 - (x - x_0 - x_1) y_2}{x_2 y_3 - x_3 y_2} \\ \end{array}}$$ Of those that have $0 \le i \le 1$, $0 \le j \le 1$, $0 \le k \le 1$, compute their $$\bbox{ z = z_0 + i z_1 + j z_2 + k z_3 }$$ and pick the one that has the smallest $z$ if smaller $z$ coordinates are in front of larger $z$ coordinates; the largest $z$ otherwise.
This is especially nice if you want to do sampling with regular real $x$ and $y$ coordinates. The above equations simplify to form $$\bbox{\begin{cases} i = C_0 + C_1 x + C_2 y \\ j = C_3 + C_4 x + C_5 y \\ k = 0 \end{cases}} , \quad \bbox{\begin{cases} i = C_6 + C_7 x + C_8 y \\ j = C_9 + C_{10} x + C_{11} y \\ k = 1 \end{cases}}$$ et cetera for each cuboid (36 scalar constants for each cuboid, in addition to the four vectors defining the cuboid), making this a viable option in a computer program.