Catmull–Clark subdivision surface: Difference between revisions

Content added Content deleted
Line 1,128: Line 1,128:


This code was written in Mathematica 8.
This code was written in Mathematica 8.

=={{header|Nim}}==
{{trans|Go}}
{{trans|Python}}
<lang Nim>import algorithm
import tables

const None = -1 # Index number used to indicate no data.

type

Point = array[3, float]
Face = seq[int]

Edge = object
pn1: int # Point number 1.
pn2: int # Point number 2.
fn1: int # Face number 1.
fn2: int # Face number 2.
cp: Point # Center point.

PointEx = object
p: Point
n: int

PointNums = tuple[pn1, pn2: int]


####################################################################################################
# Point operations.

func `+`(p1, p2: Point): Point =
## Adds points p1 and p2.
for i in 0..Point.high:
result[i] = p1[i] + p2[i]

#---------------------------------------------------------------------------------------------------

func `*`(p: Point; m: float): Point =
## Multiply point p by m.
for i in 0..Point.high:
result[i] = p[i] * m

#---------------------------------------------------------------------------------------------------

func `/`(p: Point; d: float): Point =
## Divide point p by d.
for i in 0..Point.high:
result[i] = p[i] / d

#---------------------------------------------------------------------------------------------------

func centerPoint(p1, p2: Point): Point =
## Return a point in the center of the segment ended by points p1 and p2.
for i in 0..Point.high:
result[i] = (p1[i] + p2[i]) / 2


####################################################################################################

func getFacePoints(inputPoints: seq[Point]; inputFaces: seq[Face]): seq[Point] =
## For each face, a face point is created which is the average of all the points of the face.

result.setLen(inputFaces.len)
for i, currFace in inputFaces:
var facePoint: Point
for currPointIndex in currFace:
# Add currPoint to facePoint. Will divide later.
facePoint = facePoint + inputPoints[currPointIndex]
result[i] = facePoint / currFace.len.toFloat

#---------------------------------------------------------------------------------------------------

func getEdgesFaces(inputPoints: seq[Point]; inputFaces: seq[Face]): seq[Edge] =
## Get list of edges and the one or two adjacent faces in a list.
## Also get center point of edge.

# Get edges for each face.
var edges: seq[array[3, int]]
for faceNum, face in inputFaces:
# Loop over index into face.
for pointIndex in 0..face.high:
# If not last point then edge is current point and next point
# and for last point, edge is current point and first point.
var pointNum1 = face[pointIndex]
var pointNum2 = if pointIndex < face.high: face[pointIndex + 1] else: face[0]
# Order points in edge by lowest point number.
if pointNum1 > pointNum2:
swap pointNum1, pointNum2
edges &= [pointNum1, pointNum2, faceNum]

# Sort edges by pointNum1, pointNum2, faceNum.
edges.sort(proc(a1, a2: array[3, int]): int =
result = cmp(a1[0], a2[0])
if result == 0:
result = cmp(a1[1], a2[1])
if result == 0:
result = cmp(a1[2], a2[2]))

# Merge edges with 2 adjacent faces:
# [pointNum1, pointNum2, faceNum1, faceNum2] or
# [pointNum1, pointNum2, faceNum1, None]
var eIndex = 0
var mergedEdges: seq[array[4, int]]
while eIndex < edges.len:
let e1 = edges[eIndex]
# Check if not last edge.
if eIndex < edges.high:
let e2 = edges[eIndex + 1]
if e1[0] == e2[0] and e1[1] == e2[1]:
mergedEdges &= [e1[0], e1[1], e1[2], e2[2]]
inc eIndex, 2
else:
mergedEdges &= [e1[0], e1[1], e1[2], None]
inc eIndex
else:
mergedEdges &= [e1[0], e1[1], e1[2], None]
inc eIndex

# Add edge centers.
for me in mergedEdges:
let p1 = inputPoints[me[0]]
let p2 = inputPoints[me[1]]
let cp = centerPoint(p1, p2)
result.add(Edge(pn1: me[0], pn2: me[1], fn1: me[2], fn2: me[3], cp: cp))

#---------------------------------------------------------------------------------------------------

func getEdgePoints(inputPoints: seq[Point];
edgesFaces: seq[Edge];
facePoints: seq[Point]): seq[Point] =
## For each edge, an edge point is created which is the average between the center of the
## edge and the center of the segment made with the face points of the two adjacent faces.

result.setLen(edgesFaces.len)
for i, edge in edgesFaces:
# Get center of two facepoints.
let fp1 = facePoints[edge.fn1]
# If not two faces, just use one facepoint (should not happen for solid like a cube).
let fp2 = if edge.fn2 == None: fp1 else: facePoints[edge.fn2]
let cfp = centerPoint(fp1, fp2)
# Get average between center of edge and center of facePoints.
result[i] = centerPoint(edge.cp, cfp)

#---------------------------------------------------------------------------------------------------

func getAvgFacePoints(inputPoints: seq[Point];
inputFaces: seq[Face];
facePoints: seq[Point]): seq[Point] =
## For each point calculate the average of the face points of the faces the
## point belongs to (avgFacePoints), create a list of lists of two numbers
## [facePointSum, numPoints] by going through the points in all the faces then
## create the avgFacePoints list of point by dividing pointSum(x, y, z) by numPoints

var tempPoints = newSeq[PointEx](inputPoints.len)

# Loop through faces, updating tempPoints.
for faceNum, pointNums in inputFaces:
let fp = facePoints[faceNum]
for pointNum in pointNums:
let tp = tempPoints[pointNum].p
tempPoints[pointNum].p = tp + fp
inc tempPoints[pointNum].n

# Divide to build the result.
result.setLen(inputPoints.len)
for i, tp in tempPoints:
result[i] = tp.p / tp.n.toFloat

#---------------------------------------------------------------------------------------------------

func getAvgMidEdges(inputPoints: seq[Point]; edgesFaces: seq[Edge]): seq[Point] =
## Return the average of the centers of edges the point belongs to (avgMidEdges).
## Create list with entry for each point. Each entry has two elements. One is a point
## that is the sum of the centers of the edges and the other is the number of edges.
## After going through all edges divide by number of edges.

var tempPoints = newSeq[PointEx](inputPoints.len)

# Go through edgesFaces using center updating each point.
for edge in edgesFaces:
for pointNum in [edge.pn1, edge.pn2]:
let tp = tempPoints[pointNum].p
tempPoints[pointNum].p = tp + edge.cp
inc tempPoints[pointNum].n

# Divide out number of points to get average.
result.setLen(inputPoints.len)
for i, tp in tempPoints:
result[i] = tp.p / tp.n.toFloat

#---------------------------------------------------------------------------------------------------

func getPointsFaces(inputPoints: seq[Point]; inputFaces: seq[Face]): seq[int] =
# Return the number of faces for each point.

result.setLen(inputPoints.len)

# Loop through faces updating the result.
for pointNums in inputFaces:
for pointNum in pointNums:
inc result[pointNum]

#---------------------------------------------------------------------------------------------------

func getNewPoints(inputPoints: seq[Point]; pointsFaces: seq[int];
avgFacePoints, avgMidEdges: seq[Point]): seq[Point] =
## m1 = (n - 3.0) / n
## m2 = 1.0 / n
## m3 = 2.0 / n
## newCoords = (m1 * oldCoords) + (m2 * avgFacePoints) + (m3 * avgMidEdges)

result.setLen(inputPoints.len)
for pointNum, oldCoords in inputPoints:
let n = pointsFaces[pointNum].toFloat
let (m1, m2, m3) = ((n - 3) / n, 1 / n, 2 / n)
let p1 = oldCoords * m1
let afp = avgFacePoints[pointNum]
let p2 = afp * m2
let ame = avgMidEdges[pointNum]
let p3 = ame * m3
let p4 = p1 + p2
result[pointNum] = p4 + p3

#---------------------------------------------------------------------------------------------------

func switchNums(pointNums: PointNums): PointNums =
## Return tuple of point numbers sorted least to most.

if pointNums.pn1 < pointNums.pn2: pointNums
else: (pointNums.pn2, pointNums.pn1)

#---------------------------------------------------------------------------------------------------

func cmcSubdiv(inputPoints: seq[Point];
inputFaces: seq[Face]): tuple[p: seq[Point], f: seq[Face]] =
## For each face, a face point is created which is the average of all the points of the face.
## Each entry in the returned list is a point (x, y, z).

let facePoints = getFacePoints(inputPoints, inputFaces)
let edgesFaces = getEdgesFaces(inputPoints, inputFaces)
let edgePoints = getEdgePoints(inputPoints, edgesFaces, facePoints)
let avgFacePoints = getAvgFacePoints(inputPoints, inputFaces, facePoints)
let avgMidEdges = getAvgMidEdges(inputPoints, edgesFaces)
let pointsFaces = getPointsFaces(inputPoints, inputFaces)
var newPoints = getNewPoints(inputPoints, pointsFaces, avgFacePoints, avgMidEdges)

#[Then each face is replaced by new faces made with the new points,

for a triangle face (a,b,c):
(a, edgePoint ab, facePoint abc, edgePoint ca)
(b, edgePoint bc, facePoint abc, edgePoint ab)
(c, edgePoint ca, facePoint abc, edgePoint bc)

for a quad face (a,b,c,d):
(a, edgePoint ab, facePoint abcd, edgePoint da)
(b, edgePoint bc, facePoint abcd, edgePoint ab)
(c, edgePoint cd, facePoint abcd, edgePoint bc)
(d, edgePoint da, facePoint abcd, edgePoint cd)

facePoints is a list indexed by face number so that is easy to get.

edgePoints is a list indexed by the edge number which is an index into edgesFaces.

Need to add facePoints and edgePoints to newPoints and get index into each.

Then create two new structures:

facePointNums: list indexes by faceNum whose value is the index into newPoints.

edgePointNums: dictionary with key (pointNum1, pointNum2) and value is index into newPoints.
]#

# Add face points to newPoints.
var facePointNums: seq[int]
var nextPointNum = newPoints.len # PointNum after next append to newPoints.
for facePoint in facePoints:
newPoints.add(facePoint)
facePointNums.add(nextPointNum)
inc nextPointNum

# Add edge points to newPoints.
var edgePointNums: Table[tuple[pn1, pn2: int], int]
for edgeNum, edgesFace in edgesFaces:
let pointNum1 = edgesFace.pn1
let pointNum2 = edgesFace.pn2
newPoints.add(edgePoints[edgeNum])
edgePointNums[(pointNum1, pointNum2)] = nextPointNum
inc nextPointNum

# newPoints now has the points to output. Need new faces.

#[Just doing this case for now:

for a quad face (a,b,c,d):
(a, edgePoint ab, facePoint abcd, edgePoint da)
(b, edgePoint bc, facePoint abcd, edgePoint ab)
(c, edgePoint cd, facePoint abcd, edgePoint bc)
(d, edgePoint da, facePoint abcd, edgePoint cd)

newFaces will be a list of lists where the elements are like this:
[pointNum1, pointNum2, pointNum3, pointNum4]
]#

var newFaces: seq[Face]
for oldFaceNum, oldFace in inputFaces:
# 4 points face.
if oldFace.len == 4:
let (a, b, c, d) = (oldFace[0], oldface[1], oldface[2], oldface[3])
let facePointAbcd = facePointNums[oldFaceNum]
let edgePointAb = edgePointNums[switchNums((a, b))]
let edgePointDa = edgePointNums[switchNums((d, a))]
let edgePointBc = edgePointNums[switchNums((b, c))]
let edgePointCd = edgePointNums[switchNums((c, d))]
newFaces &= @[a, edgePointAb, facePointAbcd, edgePointDa]
newFaces &= @[b, edgePointBc, facePointAbcd, edgePointAb]
newFaces &= @[c, edgePointCd, facePointAbcd, edgePointBc]
newFaces &= @[d, edgePointDa, facePointAbcd, edgePointCd]

result = (newPoints, newFaces)

#———————————————————————————————————————————————————————————————————————————————————————————————————

when isMainModule:

import strformat, strutils

let inputPoints = @[[-1.0, 1.0, 1.0],
[-1.0, -1.0, 1.0],
[1.0, -1.0, 1.0],
[1.0, 1.0, 1.0],
[1.0, -1.0, -1.0],
[1.0, 1.0, -1.0],
[-1.0, -1.0, -1.0],
[-1.0, 1.0, -1.0]]


let inputFaces = @[@[0, 1, 2, 3],
@[3, 2, 4, 5],
@[5, 4, 6, 7],
@[7, 0, 3, 5],
@[7, 6, 1, 0],
@[6, 1, 2, 4]]

var outputPoints = inputPoints
var outputFaces = inputFaces
const Iterations = 1

for i in 1..Iterations:
(outputPoints, outputFaces) = cmcSubdiv(outputPoints, outputFaces)

for p in outputPoints:
echo fmt"[{p[0]: .4f}, {p[1]: .4f}, {p[2]: .4f}]"
echo ""
for nums in outputFaces:
var s = "["
for n in nums:
s.addSep(", ", 1)
s.add(fmt"{n:2d}")
s.add(']')
echo s</lang>

{{out}}
This is the same output as that of Go.
<pre>[-0.5556, 0.5556, 0.5556]
[-0.5556, -0.5556, 0.5556]
[ 0.5556, -0.5556, 0.5556]
[ 0.5556, 0.5556, 0.5556]
[ 0.5556, -0.5556, -0.5556]
[ 0.5556, 0.5556, -0.5556]
[-0.5556, -0.5556, -0.5556]
[-0.5556, 0.5556, -0.5556]
[ 0.0000, 0.0000, 1.0000]
[ 1.0000, 0.0000, 0.0000]
[ 0.0000, 0.0000, -1.0000]
[ 0.0000, 1.0000, 0.0000]
[-1.0000, 0.0000, 0.0000]
[ 0.0000, -1.0000, 0.0000]
[-0.7500, 0.0000, 0.7500]
[ 0.0000, 0.7500, 0.7500]
[-0.7500, 0.7500, 0.0000]
[ 0.0000, -0.7500, 0.7500]
[-0.7500, -0.7500, 0.0000]
[ 0.7500, 0.0000, 0.7500]
[ 0.7500, -0.7500, 0.0000]
[ 0.7500, 0.7500, 0.0000]
[ 0.7500, 0.0000, -0.7500]
[ 0.0000, -0.7500, -0.7500]
[ 0.0000, 0.7500, -0.7500]
[-0.7500, 0.0000, -0.7500]

[ 0, 14, 8, 15]
[ 1, 17, 8, 14]
[ 2, 19, 8, 17]
[ 3, 15, 8, 19]
[ 3, 19, 9, 21]
[ 2, 20, 9, 19]
[ 4, 22, 9, 20]
[ 5, 21, 9, 22]
[ 5, 22, 10, 24]
[ 4, 23, 10, 22]
[ 6, 25, 10, 23]
[ 7, 24, 10, 25]
[ 7, 16, 11, 24]
[ 0, 15, 11, 16]
[ 3, 21, 11, 15]
[ 5, 24, 11, 21]
[ 7, 25, 12, 16]
[ 6, 18, 12, 25]
[ 1, 14, 12, 18]
[ 0, 16, 12, 14]
[ 6, 18, 13, 23]
[ 1, 17, 13, 18]
[ 2, 20, 13, 17]
[ 4, 23, 13, 20]</pre>


=={{header|OCaml}}==
=={{header|OCaml}}==