View source on GitHub | See Demo | Toggle Types language heron:std:0.1; // The following pages are inspirations for some of this // https://prideout.net/blog/?p=44 // https://github.com/mrdoob/three.js/blob/master/src/geometries/ParametricGeometry.js // https://github.com/mrdoob/three.js/blob/master/examples/js/ParametricGeometries.js // https://paulbourke.net/geometry/ module heron:geometry.mesh:0.1 { import heron:std.array:0.1; import heron:geometry.vector:0.1; // Alternative constructors (Array<Float3> -> Mesh)function mesh(vertexBuffer) = mesh(vertexBuffer, vertexBuffer.indices); ('a 'b -> 'c)function mesh(vertexBuffer, indexBuffer) = mesh(vertexBuffer); (Array<Float3> Array<Int> -> Mesh)function mesh(vertexBuffer, indexBuffer) = mesh(vertexBuffer, indexBuffer, origin.repeat(0)); (Array<Float3> Array<Int> Array<Float3> -> Mesh)function mesh(vertexBuffer, indexBuffer, uvBuffer) = mesh(vertexBuffer, indexBuffer, uvBuffer, origin.repeat(0)); (Array<Float3> Array<Int> Array<Float3> Array<Float3> -> Mesh)function mesh(vertexBuffer, indexBuffer, uvBuffer, colorBuffer) = mesh(vertexBuffer, indexBuffer, uvBuffer, colorBuffer, origin.repeat(0)) .computeVertexNormals(); (Mesh Array<Float3> -> Mesh)function setVertices(m, points) = mesh(points, m.indexBuffer, m.uvBuffer, m.colorBuffer, m.normalBuffer); (Mesh Array<Float3> -> Mesh)function setVertexColors(m, colors) = mesh(m.vertexBuffer, m.indexBuffer, m.uvBuffer, colors, m.normalBuffer); (Mesh Array<Float3> -> Mesh)function setVertexUVs(m, uvs) = mesh(m.vertexBuffer, m.indexBuffer, uvs, m.colorBuffer, m.normalBuffer); (Mesh Array<Float3> -> Mesh)function setVertexNormals(m, normals) = mesh(m.vertexBuffer, m.indexBuffer, m.uvBuffer, m.colorBuffer, normals); // Platonic solids var tetrahedron = mesh( [ 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1 ].toVectors, [ 2, 1, 0, 0, 3, 2, 1, 3, 0, 2, 3, 1 ]); var cube = mesh( [ // front -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, // back -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0 ].toVectors, [ // front 0, 1, 2, 2, 3, 0, // right 1, 5, 6, 6, 2, 1, // back 7, 6, 5, 5, 4, 7, // left 4, 0, 3, 3, 7, 4, // bottom 4, 5, 1, 1, 0, 4, // top 3, 2, 6, 6, 7, 3 ]); // https://github.com/mrdoob/three.js/blob/master/src/geometries/OctahedronGeometry.js var octahedron = mesh( [ 1, 0, 0, -1, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 1, 0, 0, -1 ].toVectors, [ 0, 2, 4, 0, 4, 3, 0, 3, 5, 0, 5, 2, 1, 2, 5, 1, 5, 3, 1, 3, 4, 1, 4, 2 ]); // https://github.com/mrdoob/three.js/blob/master/src/geometries/DodecahedronGeometry.js var dodecahedron = var t = (1 + sqrt(5)) / 2 in var r = 1 / t in mesh([ -1, -1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, // (0, +/-1/theta, +/-theta) 0, -r, -t, 0, -r, t, 0, r, -t, 0, r, t, // (+/-1/theta, +/-theta, 0) -r, -t, 0, -r, t, 0, r, -t, 0, r, t, 0, // (+/-theta, 0, +/-1/theta) -t, 0, -r, t, 0, -r, -t, 0, r, t, 0, r ].toVectors, [ 3, 11, 7, 3, 7, 15, 3, 15, 13, 7, 19, 17, 7, 17, 6, 7, 6, 15, 17, 4, 8, 17, 8, 10, 17, 10, 6, 8, 0, 16, 8, 16, 2, 8, 2, 10, 0, 12, 1, 0, 1, 18, 0, 18, 16, 6, 10, 2, 6, 2, 13, 6, 13, 15, 2, 16, 18, 2, 18, 3, 2, 3, 13, 18, 1, 9, 18, 9, 11, 18, 11, 3, 4, 14, 12, 4, 12, 0, 4, 0, 8, 11, 9, 5, 11, 5, 19, 11, 19, 7, 19, 5, 14, 19, 14, 4, 19, 4, 17, 1, 12, 14, 1, 14, 5, 1, 5, 9 ]); // https://github.com/mrdoob/three.js/blob/master/src/geometries/IcosahedronGeometry.js var icosahedron = var t = (1 + sqrt(5)) / 2 in mesh([ -1, t, 0, 1, t, 0, -1, -t, 0, 1, -t, 0, 0, -1, t, 0, 1, t, 0, -1, -t, 0, 1, -t, t, 0, -1, t, 0, 1, -t, 0, -1, -t, 0, 1 ].toVectors, [ 0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 0, 10, 11, 1, 5, 9, 5, 11, 4, 11, 10, 2, 10, 7, 6, 7, 1, 8, 3, 9, 4, 3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9, 4, 9, 5, 2, 4, 11, 6, 2, 10, 8, 6, 7, 9, 8, 1 ]); // Parametric geometry creation // Given a number of points (vectors) arranged as a quad strip of "rows" // will return a set of indices representing the triangulated indices. (Array<'a> Int Bool Bool -> Array<Int>)function quadStripToMeshIndices(vertices, rows, connectRows, connectCols) { var cols = vertices.count / rows; var nr = connectRows ? rows : rows-1; var nc = connectCols ? cols : cols-1; var indices = [].mutable; for (var row in 0..nr) { for (var col in 0..nc) { // Rows increase from bottom to top // Columns increase from left to right // row r + 1 = ... d c ... // row r = ... a b ... var a = col + row * cols; var b = (col+1) % (cols) + row * cols; var c = (col+1) % (cols) + (row+1) % (rows) * cols; var d = col + (row+1) % (rows) * cols; indices = indices.pushMany([a, b, d]); indices = indices.pushMany([b, c, d]); } } return indices.immutable; } // Converts from UV coordinates to a float3 (Float2 -> Float3)function vector(uv: Float2) = float3(-uv.x.cos * uv.y.sin, uv.x.cos, uv.x.sin * uv.y.sin); // Works for Float, Float2, Float3, and Float4 ('a 'a 'a -> 'a)function rescale(v, from, length) = from + (v * length); // Given a function that converts UV coordinates to vectors, creates an // array of points (uCount x vCount) starting at uStart,vStart (0,0) going to // uLength, vLength (1,1) ((Float Float -> Float3) Int Int Float Float Float Float Bool Bool -> Mesh)function meshFromUV(f, uCount, vCount, uStart, vStart, uLength, vLength, uJoin, vJoin) { var uMax = uJoin ? uCount.float : (uCount - 1).float; var vMax = vJoin ? vCount.float : (vCount - 1).float; var uvs = cartesianProduct(0..uCount, 0..vCount, (u, v) => vector(u / uMax * uLength + uStart, v / vMax * vLength + vStart, 0)); var points = uvs.map(uvw => f(uvw.x, uvw.y)); var normals = uvs.map(uvw => normalFromUV(uvw.x, uvw.y, f)); var indices = quadStripToMeshIndices(points, vCount, uJoin, vJoin); return mesh(points, indices, uvs, origin.repeat(0), normals); } ((Float Float -> Float3) Int -> Mesh)function meshFromUV(f, segments) = meshFromUV(f, segments, true); ((Float Float -> Float3) Int Bool -> Mesh)function meshFromUV(f, segments, join) = meshFromUV(f, segments, segments, 0.0, 0.0, 1.0, 1.0, join, join); // Given UV coordinates on the surface of a sphere u=[0,1), v=[0,1) computes the 3D location. (Float Float -> Float3)function spherePoint(u, v) = vector(-cos(u*2.0*pi) * sin(v*2.0*pi), cos(v*2.0*pi), sin(u*2.0*pi) * sin(v*2.0*pi)); (Int -> Mesh)function sphere(segments) = meshFromUV(spherePoint, segments); ( -> Mesh)function sphere() = sphere(32); // Given UV coordinates on the surface of a cylinder u=[0,1), v=[0,1) computes the 3D location. (Float Int -> Float3)function cylinderPoint(u, v) = vector(sin(u*2.0*pi), v * 2, cos(u*2.0*pi)); (Int -> Mesh)function cylinder(segments) = meshFromUV(cylinderPoint, segments); ( -> Mesh)function cylinder() = cylinder(16); (Float Float Int -> Mesh)function torus(r1, r2, segments) = meshFromUV((u, v) => torusPoint(u, v, r1, r2), segments); // Given UV coordinates u=[0,1), v=[0,1), and a major radius (donut) // and a minor radius (tube) computes the 3D location of the torus (Float Float Float Float -> Float3)function torusPoint(u, v, r1, r2) = vector( (r1 + r2 * cos(v*2.0*pi)) * cos(u*2.0*pi), (r1 + r2 * cos(v*2.0*pi)) * sin(u*2.0*pi), r2 * sin(v*2.0*pi)); (Float 'a (Float 'a -> Float3) Float3 Float -> Float3)function tangentFromUV(u, v, f, p: Float3, eps: Float) = u - eps >= 0 ? p - f(u - eps, v) : f(u + eps, v) - p; ('a Float ('a Float -> Float3) Float3 Float -> Float3)function cotangentFromUV(u, v, f, p: Float3, eps: Float) = v - eps >= 0 ? p - f(u, v - eps) : f(u, v + eps) - p; // Given a function that generates coordinates from UV space will compute a local normal. (Float Float (Float Float -> Float3) -> Float3)function normalFromUV(u, v, f) = var eps = 0.0001 in var p = f(u, v) in cross(tangentFromUV(u, v, f, p, eps), cotangentFromUV(u, v, f, p, eps)).normal; ( -> Mesh)function torus() = torus(2, 0.5, 32); (Mesh -> Int)function vertexCount(mesh) = mesh.vertexBuffer.count; (Mesh -> Int)function faceCount(mesh) = mesh.indexBuffer.count / 3; (Array<Float> -> Array<Float3>)function toVectors(xs) = (0 .. xs.count/3).map(i => vector(xs[i*3], xs[i*3+1], xs[i*3+2])); (Mesh (Float3 -> Float3) -> Mesh)function transform(m, f) = m.setVertices(m.vertexBuffer.map(f)); (Mesh Float3 -> Mesh)function translate(m, amount) = m.transform(v => v + amount); (Mesh Array<Float3> -> Mesh)function translate(m, vectors) = m.setVertices(m.vertexBuffer.zip(vectors, (p, v) => p + v)); (Mesh Float3 -> Mesh)function scale(m, amount) = m.transform(v => v * amount); (Float Float -> Float3)function kleinPoint(a, b) { var u = a * pi * 2.0; var v = b * pi * 2.0; var x = 0.0, y = 0.0, z = 0.0; if (u < pi) { x = 3.0 * u.cos * (1.0 + u.sin) + (2.0 * (1.0 - u.cos / 2.0)) * u.cos * v.cos; z = -8.0 * u.sin - 2.0 * (1.0 - u.cos / 2.0) * u.sin * v.cos; } else { x = 3.0 * u.cos * (1.0 + u.sin) + (2.0 * (1.0 - u.cos / 2.0)) * cos(v + pi); z = -8.0 * u.sin; } y = -2.0 * (1.0 - u.cos / 2.0) * v.sin; // Scaled down, because it is too big compared to other primitives // TODO: find a more well thoughts out scale factor (is it pi?) return vector(x / 4.0, y / 4.0, z / 4.0); } ( -> Mesh)function klein() = meshFromUV(kleinPoint, 32, false); (Float Float -> Float3)function planeXYPoint(u, v) = vector(u, v, 0); (Float Float -> Float3)function planeXZPoint(u, v) = vector(u, 0, v); (Float Float -> Float3)function planeYZPoint(u, v) = vector(0, u, v); (Float Float -> Float3)function planeYXPoint(u, v) = vector(v, u, 0); (Float Float -> Float3)function planeZXPoint(u, v) = vector(v, 0, u); (Float Float -> Float3)function planeZYPoint(u, v) = vector(0, v, u); ( -> Mesh)function plane() = meshFromUV(planeXYPoint, 16, false); (Float Float -> Float3)function mobiusPoint(a, b) { var u = a - 0.5; var v = b * 2.0 * pi; return vector( v.cos * (2 + u * cos( v / 2 )), v.sin * (2 + u * cos( v / 2 )), u * sin( v / 2 )); } ( -> Mesh)function mobius() = meshFromUV(mobiusPoint, 20, false); (Mesh Int Int -> Float3)function facePoint(mesh, f, i) = mesh.vertexBuffer[mesh.indexBuffer[f * 3 + i]]; // Works only if the mesh has non-degenerate faces (faces of ) (Mesh Int -> Float3)function faceNormal(mesh, f) = cross(mesh.faceSide1(f), mesh.faceSide2(f)).normal; (Mesh Int -> Float3)function faceSide1(mesh, f) = mesh.facePoint(f, 1) - mesh.facePoint(f, 0); (Mesh Int -> Float3)function faceSide2(mesh, f) = mesh.facePoint(f, 2) - mesh.facePoint(f, 0); (Mesh -> Array<Float3>)function faceNormals(mesh) = (0..mesh.faceCount).map(i => mesh.faceNormal(i)); (Mesh -> Mesh)function computeVertexNormals(mesh) { var sums = origin.repeat(mesh.vertexCount).mutable; var counts = repeat(0, mesh.vertexCount).mutable; for (var f in 0..mesh.faceCount) { var normal = mesh.faceNormal(f); for (var i in 0..3) { var index = mesh.indexBuffer[f*3 + i]; sums[index] += normal; counts[index] += 1; } } return mesh.setVertexNormals(zip(sums, counts, (a, b) => (a / b.float.vector).normal)); } // Rotation is represented as a quaternion (Float3 Float3 Float3 -> Float3)function applyRotation(pos, rotXYZ, rotW) = pos + 2.0.vector * cross( rotXYZ, cross( rotXYZ, pos ) + rotW * pos ); // Rotation is represented as a quaternion (Float3 Float3 Float3 Float3 Float3 -> Float3)function applyTRS(pos, trans, rotXYZ, rotW, scale) = applyRotation(pos * scale, rotXYZ, rotW) + trans; }