Created
April 6, 2023 06:35
-
-
Save theGOTOguy/dca3d0e9fc7c8446a8951234d35633b5 to your computer and use it in GitHub Desktop.
Bezier Bean Source Code for TinkerCAD "Cloud Gate"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Convenience Declarations For Dependencies. | |
// 'Core' Is Configured In Libraries Section. | |
var Conversions = Core.Conversions; | |
var Debug = Core.Debug; | |
var Path2D = Core.Path2D; | |
var Point2D = Core.Point2D; | |
var Point3D = Core.Point3D; | |
var Matrix2D = Core.Matrix2D; | |
var Matrix3D = Core.Matrix3D; | |
var Mesh3D = Core.Mesh3D; | |
var Plugin = Core.Plugin; | |
var Tess = Core.Tess; | |
var Sketch2D = Core.Sketch2D; | |
var Solid = Core.Solid; | |
var Vector2D = Core.Vector2D; | |
var Vector3D = Core.Vector3D; | |
params = [ | |
// Basic parameters. | |
{ "id": "width", "displayName": "Width", "type": "length", "rangeMin": 1.0, "rangeMax": 200.0, "default": 6.5 }, | |
{ "id": "length", "displayName": "Length", "type": "length", "rangeMin": 1.0, "rangeMax": 200.0, "default": 10.0 }, | |
{ "id": "height", "displayName": "Height", "type": "length", "rangeMin": 1.0, "rangeMax": 200.0, "default": 5.0 }, | |
{ "id": "ellipse", "displayName": "Ellipse", "type": "float", "rangeMin": 1.0, "rangeMax": 3.0, "default": 2.05 }, | |
// Parameters for the omphalos control point. | |
{ "id": "oheight", "displayName": "Omphalos height", "type": "length", "rangeMin": 1.0, "rangeMax": 100.0, "default": 4.1 }, | |
{ "id": "oradius", "displayName": "Omphalos radius", "type": "length", "rangeMin": 1.0, "rangeMax": 50.0, "default": 1.3 }, | |
{ "id": "opinch", "displayName": "Omphalos pinch", "type": "length", "rangeMin": 1.0, "rangeMax": 50.0, "default": 0.5 }, | |
// Parameters for lower control point. | |
{ "id": "archsoftness", "displayName": "Arch Softness", "type": "float", "rangeMin": 0.0, "rangeMax": 0.5, "default": 0.35}, | |
{ "id": "archheight", "displayName": "Arch height", "type": "length", "rangeMin": 1.0, "rangeMax": 100.0, "default": 1.4 }, | |
{ "id": "archradius", "displayName": "Proportion of radius at arch", "type": "float", "rangeMin": 0.01, "rangeMax": 1, "default": 0.7}, | |
// Parameters for outermost control point. | |
{ "id": "lheight", "displayName": "Height at length", "type": "length", "rangeMin": 1.0, "rangeMax": 200.0, "default": 1.5}, | |
{ "id": "wheight", "displayName": "Height at width", "type": "length", "rangeMin": 1.0, "rangeMax": 200.0, "default": 2.5}, | |
// Round | |
{ "id": "lfullness", "displayName": "Lengthwise Fullness", "type": "float", "rangeMin": 0.01, "rangeMax": 1, "default": 0.54}, | |
{ "id": "wfullness", "displayName": "Widthwise Fullness", "type": "float", "rangeMin": 0.01, "rangeMax": 1, "default": 0.58}, | |
{ "id": "ltoproundness", "displayName": "Lengthwise Roundness", "type": "float", "rangeMin": 0.01, "rangeMax": 1, "default": 0.54}, | |
{ "id": "wtoproundness", "displayName": "Widthwise Roundness", "type": "float", "rangeMin": 0.01, "rangeMax": 1, "default": 0.53}, | |
{ "id": "lowroundness", "displayName": "Underside Roundness", "type": "float", "rangeMin": 0.01, "rangeMax": 1, "default": 0.54}, | |
// Resolution. | |
{ "id": "resolution", "displayName": "Resolution", "type": "int", "rangeMin": 4, "rangeMax": 20, "default": 60} | |
]; | |
function binom(n, k) { | |
var coeff = 1; | |
for (var i = n-k+1; i <= n; i++) coeff *= i; | |
for (var i = 1; i <= k; i++) coeff /= i; | |
return coeff; | |
} | |
function bezier(points, controlPoints, include_first, include_last, resolution) { | |
var first = include_first ? 1 : 0; | |
var last = include_last ? resolution : resolution - 1; | |
for (var i = first; i <= last; i++) { | |
var thisPoint; | |
var t; | |
thisPoint = [0, 0]; | |
t = i / resolution; | |
for (var j = 0; j < controlPoints.length; j++) { | |
var coefficient; | |
coefficient = Math.pow(1 - t, controlPoints.length - j - 1) * | |
Math.pow(t, j) * | |
binom(controlPoints.length - 1, j); | |
thisPoint[0] += coefficient * controlPoints[j][0]; | |
thisPoint[1] += coefficient * controlPoints[j][1]; | |
} | |
points.push(thisPoint); | |
} | |
return points; | |
} | |
function get2DPointsForSlice(height, length, baseheight, baseradius, rheight, roofheight, oradius, oheight, opinch, toproundness, lowroundness, fullness, resolution) { | |
var points = []; | |
// Top to outer | |
bezier(points, [[0, height], | |
[length * fullness, height], | |
[length, rheight + (height - rheight) * toproundness], | |
[length, rheight]], | |
false, true, resolution); | |
// Outer to bottom | |
bezier(points, [[length, rheight], | |
[length, baseheight + (rheight - baseheight) * (1 - lowroundness)], | |
[baseradius + (length - baseradius) * lowroundness, baseheight], | |
[baseradius, baseheight]], | |
false, true, resolution); | |
// Bottom to inside of omphalos | |
bezier(points, [[baseradius, baseheight], | |
[oradius - opinch, baseheight], // This is just a guess. It may need further tweaking. | |
[oradius, oheight], | |
[0, oheight]], | |
false, false, resolution); | |
/* | |
Debug.log(points); | |
Debug.log(points[0]); | |
*/ | |
return points; | |
} | |
// This could be improved with a binary search, if necessary. | |
function lookup_x(curvepoints, x, max_i) { | |
for (var i = 0; i < max_i; i++) { | |
if (curvepoints[i][0] >= x) | |
return curvepoints[i][1]; | |
} | |
return curvepoints[0][max_i - 1]; | |
} | |
// This function simply gets the points in 2D, and then puts them on the right plane. | |
function get3DPointsForSlice(params, angle) { | |
var slicelength = params['length'] / 2; // Remember, each slice is only half the bean (because we are in polar coordinates) | |
var slicewidth = params['width'] / 2; | |
var e = params['ellipse']; | |
var r = slicelength * slicewidth / ( | |
Math.pow(Math.pow(Math.abs(slicelength * Math.cos(angle)), e) + | |
Math.pow(Math.abs(slicewidth * Math.sin(angle)), e), | |
1 / e)); | |
// Base height for this slice. | |
// I'm going to use the Bezier function also to get the base height, since I want it to be "flat" for a while. | |
var archpoints = []; | |
var archresolution = params['resolution'] * 8; // Resolution * 4 is an arbitrary choice. Actual resolution used should be at least as big as resolution. | |
bezier(archpoints, [[0, params['archheight']], | |
[(Math.PI / 2) * params['archsoftness'], params['archheight']], | |
[(Math.PI / 2) * (1 - params['archsoftness']), 0], | |
[Math.PI / 2, 0]], | |
true, true, archresolution); | |
var baseheight; | |
if (Math.PI / 2 >= angle) { | |
baseheight = lookup_x(archpoints, angle, archresolution); | |
} else if (Math.PI >= angle) { | |
baseheight = lookup_x(archpoints, Math.PI - angle, archresolution); | |
} else if (3 * Math.PI / 2 >= angle) { | |
baseheight = lookup_x(archpoints, angle - Math.PI, archresolution); | |
} else { | |
baseheight = lookup_x(archpoints, 2 * Math.PI - angle, archresolution); | |
} | |
//var baseheight = params['archheight'] * Math.pow(Math.cos(angle), 2); | |
// Base radius for this slice. | |
var baseradius = r * params['archradius']; | |
var rheight = Math.sqrt(Math.pow(params['lheight'] * Math.sin(angle), 2) + Math.pow(params['wheight'] * Math.cos(angle), 2)); | |
var fullness = Math.pow(Math.pow(Math.abs(params['lfullness'] * Math.sin(angle)), 2) + Math.pow(Math.abs(params['wfullness'] * Math.cos(angle)), 2), | |
1 / 2); | |
var toproundness = Math.pow(Math.pow(Math.abs(params['ltoproundness'] * Math.sin(angle)), 2) + Math.pow(Math.abs(params['wtoproundness'] * Math.cos(angle)), 2), | |
1 / 2); | |
var points2d = get2DPointsForSlice(params['height'], r, baseheight, baseradius, rheight, params['roofheight'], params['oradius'], params['oheight'], params['opinch'], toproundness, params['lowroundness'], fullness, params['resolution']); | |
var points = []; | |
for (var i=0; i<points2d.length; i++) { | |
points.push([points2d[i][0] * Math.cos(angle), points2d[i][0] * Math.sin(angle), points2d[i][1]]); | |
} | |
return points; | |
} | |
function drawSlice(mesh, last_slice, next_slice, height, oheight) { | |
// Parameter slices MUST have the same size. | |
// The very first points connect to (0, 0, height). | |
mesh.triangle([0, 0, height], last_slice[0], next_slice[0]); | |
// The rest of the points make little boxes. | |
for (var i=0; i<next_slice.length-1; i++) { | |
mesh.triangle(next_slice[i], last_slice[i], last_slice[i+1]); | |
mesh.triangle(last_slice[i+1], next_slice[i+1], next_slice[i]); | |
} | |
// The very last points connect to (0, 0, oheight). | |
mesh.triangle(next_slice[next_slice.length - 1], last_slice[last_slice.length - 1], [0, 0, oheight]); | |
} | |
function process(params) { | |
var mesh = new Mesh3D(); | |
var first_slice = get3DPointsForSlice(params, 0); | |
var last_slice = first_slice; | |
for (var i=1; i<params['resolution'] * 4; i++) { | |
var angle = 2.0 * Math.PI * i / (params['resolution'] * 4); | |
var slice = get3DPointsForSlice(params, angle); | |
drawSlice(mesh, last_slice, slice, params['height'], params['oheight']); | |
last_slice = slice; | |
} | |
// Now draw in the final slice connecting back to the first one. | |
drawSlice(mesh, last_slice, first_slice, params['height'], params['oheight']); | |
return Solid.make(mesh); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I made this shape and wrote this source code for the TinkerCad "bezier bean" custom shape generator. Please enjoy.
https://www.tinkercad.com/things/7ECRdzjIHsa-cloud-gate
https://www.thingiverse.com/thing:684126