lambdaway
::
pforms4
1
|
list
|
login
|
load
|
|
{style body, #page_frame, #page_content, .page_menu { border:0 solid #444; box-shadow:0 0 0; background:#444; color:#fff; } } _h1 [[pforms]] | [[pforms2]] | [[pforms3]] | pforms4 | [[pforms5]] {div {@ id="W3D"}{W3D -40 0 75 1.0 500}} {br}{parameter R/ox rox 320 0 360 10} {br}{parameter R/oy roy 0 0 360 10} {br}{parameter R/oz roz 40 0 360 10} {br}{parameter scale scale 1.0 0.1 2.0 0.1} _p Lambdatalk has natively a set of functions to [[plot]] 2D SVG graphics. In this page we add new functions to create and display 3D curves: _ul four primitives {code W2D.init, W3D.polyline, W3D.stretch, W3D.blossom}, _ul five user defined functions, {code W3D, W3D.open, W3D.frame, W3D.addForm, camera}, _p with which we can create a 3D SVG context and add 3D polylines and bézier curves. Points are defined in a 4D space and projected in our 3D space, allowing to create {b rational curves}, for instance conics. _h1 1) initializing the 3Dworld {pre '{def W3D {lambda {:rox :roy :roz :scal :pers} {{W3D.open 580 580} // a 580x580 SVG window {{W3D.frame 580 580} // 2D scale {W3D.init :rox // rotation on Ox :roy // rotation on Oy :roz // rotation on Oz :scal // scale :pers} // perspective ... adding forms }}}} -> {def W3D {lambda {:ox :oy :oz :scal :pers} {{W3D.open 580 580} {{W3D.frame 580 580} {W3D.init :ox :oy :oz :scal :pers} {W3D.addForm 1 #fff [0,0,0,1] {W3D.point 0 0 0 1 150}} {W3D.addForm 2 #888 [0,0,0,1] {cube 150}} {W3D.addForm 2 #fff [0,0,0,1] {polygon 60 3}} {W3D.addForm 2 #0ff [0,0,0,1] {polygon 80 6}} {W3D.addForm 2 #ff0 [0,0,0,1] {polygon 120 50}} {W3D.addForm 9 rgba(255,255,255,0.5) [0,0,90,1] {W3D.blossom 5 {W3D.stretch -0.1 1.8 [{p0},{p1},{p2},{p3}]}}} {S.map {lambda {:i} {W3D.addForm 1 rgb(:i,{- 250 :i},{- 250 :i}) [0,0,:i,1] {W3D.blossom 4 [{p0},{p1},{p2},{p3}]}}} {S.serie 0 270 3}} {W3D.addForm 2 #fff [0,0,0,1] {W3D.point -170 -170 -150 1 10}} }}}} '{div {@ id="W3D"} {W3D -40 0 80 1.00 500} } '{parameter R/ox rox 320 0 360 10} '{parameter R/oy roy 0 0 360 10} '{parameter R/oz roz 40 0 360 10} '{parameter scale scale 1.0 0.1 2.0 0.1} } _p Three sliders can be used to rotate the world on Ox, Oy, Oz. _h1 2) adding 3D curves _p The general pattern is: {pre '{addForm width // line width/thickness color // line color #rgb or rgb(r,g,b) [rox,roy,roz,scale] // transformations [p0,p1,...,pn] // where pi = [x,y,z,w] } } _h1 3) examples _p The picture on top of this page is created using the following functions. Play with the sliders to roll around. _h2 1) a 3D point _p Points are defined as {code [x,y,z,w]} arrays and displayed as three small crossing segments. By default the fourth value {code w} is set to {b 1}. Rational curves, for instance conics, have this fourth value different from 1. {pre '{def W3D.point {lambda {:x :y :z :w :s} [[{- :x :s},:y,:z,:w], [{+ :x :s},:y,:z,:w], [:x,:y,:z,:w], [:x,{- :y :s},:z,:w], [:x,{+ :y :s},:z,:w], [:x,:y,:z,:w], [:x,:y,{- :z :s},:w], [:x,:y,{+ :z :s},:w]]}} -> {def W3D.point {lambda {:x :y :z :w :s} [[{- :x :s},:y,:z,:w],[{+ :x :s},:y,:z,:w],[:x,:y,:z,:w], [:x,{- :y :s},:z,:w],[:x,{+ :y :s},:z,:w],[:x,:y,:z,:w], [:x,:y,{- :z :s},:w],[:x,:y,{+ :z :s},:w]]}} 1.1) adding a 3D point '{addForm 1 // line width #000 // line color [rox,roy,roz,scale] // transformations {W3D.point -100 // x -100 // y -100 // z 1 // w (equal to 1 by default) 10}} // segments' half length 1.2) adding the "world" axis (just a big point) '{W3D.addForm 1 #000 [0,0,0,0] {W3D.point 0 0 0 1 150}} } _h2 2) creating a polyline _p Polylines are defined as arrays of points, {code [p0,p1,...,pn]} {pre 2.1) the "world" cube, a polyline built on 9 segments '{def cube {lambda {:d} [[-:d,-:d,-:d,1], [:d,-:d,-:d,1], [:d,:d,-:d,1], [-:d,:d,-:d,1], [-:d,:d,:d,1], [:d,:d,:d,1], [:d,-:d,:d,1], [-:d,-:d,:d,1], [-:d,-:d,-:d,1]] }} -> {def cube {lambda {:d} [[-:d,-:d,-:d,1], [:d,-:d,-:d,1], [:d,:d,-:d,1], [-:d,:d,-:d,1], [-:d,:d,:d,1], [:d,:d,:d,1], [:d,-:d,:d,1], [-:d,-:d,:d,1], [-:d,-:d,-:d,1]] }} 2.2) adding the world cube '{W3D.addForm 2 #888 [0,0,0,0] {cube 150}} 2.3) creating a regular polygon, from triangle to circle '{def polygon {def polygon.r {lambda {:r :k :i} {if {< :i 0} then [{* :r {cos {* :k :i}}},{* :r {sin {* :k :i}}},0,1] else [{* :r {cos {* :k :i}}},{* :r {sin {* :k :i}}},0,1], {polygon.r :r :k {- :i 1}} }}} {lambda {:r :n} [{polygon.r :r {/ {* 2 {PI}} :n} :n}]}} -> {def polygon {def polygon.r {lambda {:r :k :i} {if {< :i 0} then [{* :r {cos {* :k :i}}},{* :r {sin {* :k :i}}},0,1] else [{* :r {cos {* :k :i}}},{* :r {sin {* :k :i}}},0,1], {polygon.r :r :k {- :i 1}} }}} {lambda {:r :n} [{polygon.r :r {/ {* 2 {PI}} :n} :n}] }} 2.4) adding a 60 points polygon, close to a circle '{W3D.addForm 3 #f00 [0,0,0,0] {polygon 120 60}} } _p Note that this circle could have been defined as rational bézier curve built on five points. _h2 3) creating rational bézier cubic curves _p Bézier curves are controlled by an array of control points, {code [p0,p1,...,pn]} and a number defining the level of recursion. The {code W3D.blossom} function applies the {b de Casteljau} algorithm, blossoms a tree of polylines and returns the concatenation of these polylines. Bézier curves are defined in an interval set to {code [0,1]}. The {code W3D.stretch} function modifies this interval to any {code [a,b]} values. _p More explanations in [[decasteljau]]. {pre 3.1) defining 4 control 4Dpoints '{def p0 [-150,-150,-150,1]} -> {def p0 [-150,-150,-150,1]} '{def p1 [ 150,-150,-150,1]} -> {def p1 [ 150,-150,-150,1]} '{def p2 [ 150,-150, 150,1]} -> {def p2 [ 150,-150, 150,1]} '{def p3 [ 150, 150, 150,1]} -> {def p3 [ 150, 150, 150,1]} 3.2) adding the cubic curve '{addForm 5 // line width #f00 or rgb(255,0,0) // line color [0,0,0,0] // no transformation {W3D.blossom 4 // level of recursion {W3D.stretch // control polygon stretched -1.5 // from t = -1.5 1.5 // to t =. 1.5 [{p0},{p1},{p2},{p3}] // control polygon }}} 3.3) adding 28 rational bézier cubic curves rotating around Oz '{S.map {lambda {:i} // repeat ... {W3D.addForm // add curve 1 // line width rgb(:i,0,{- 250 :i}) // variable color [0,0,:i,0] // transform {W3D.blossom 4 // level of recursion [{p0},{p1},{p2},{p3}] }}} {S.serie 0 270 10}} // ... from 0 to 270 step 10 } _h1 4) the underlying machinery _p Externalizing the following lambdatalk and javascriptcode in some {code lib_W3D} wiki page could allow 3D graphics anywhere in the wiki. {pre °° {def parameter {lambda {:name :id :val :min :max :step} {center :name : {input {@ id=":id" type="text" value=":val" size="5" style="background:#444; color:#fff; border:0px solid"}} {input {@ type="range" min=":min" max=":max" value=":val" step=":step" style="width:110px; vertical-align:middle;" oninput="camera(':id', this.value)"}}}}} {def W3D.open {lambda {:w :h} svg {@ width=":w" height=":h" style="box-shadow:0 0 8px #000; background:#666; border:0px solid #fff;"}}} {def W3D.frame {lambda {:w :h} g {@ id="forms" transform="translate({/ :w 2},{/ :h 2})"}}} {def W3D.addForm {lambda {:w :col :m :points} {path {@ d="{W3D.polyline :m :points}" fill="transparent" stroke-width=":w" stroke=":col"}}}} } {script var camera = function(id,val) { var rox = (id === 'rox')? val : document.getElementById("rox").value; var roy = (id === 'roy')? val : document.getElementById("roy").value; var roz = (id === 'roz')? val : document.getElementById("roz").value; var sca = (id === 'scale')? val : document.getElementById("scale").value; document.getElementById(id).value = val; var World = "{W3D " + rox + " " + roy + " " + roz + " " + sca + " " + " 500}"; document.getElementById('W3D').innerHTML = LAMBDATALK.eval_forms( World ) }; var W3D = (function() { var CAM = []; // W3D global : 3D camera function init( rox,roy,roz,scal,pers ) { CAM = [ [1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1] ]; CAM = product( CAM, perspective( pers ) ); CAM = product( CAM, rotate( 'OX', rox ) ); CAM = product( CAM, rotate( 'OY', roy ) ); CAM = product( CAM, rotate( 'OZ', roz ) ); CAM = product( CAM, scale( scal, -scal, -scal ) ); } function rotate( axe, a ) { // R4 -> R4 var a = 2*Math.PI/360*a, cosa = Math.cos(a), sina = Math.sin(a), m = ''; if (axe == 'OX') m = [ [1,0,0,0], [0,cosa,-sina,0], [0,sina,cosa,0], [0,0,0,1] ]; if (axe == 'OY') m = [ [cosa,0,sina,0], [0,1,0,0], [-sina,0,cosa,0], [0,0,0,1] ]; if (axe == 'OZ') m = [ [cosa,-sina,0,0], [sina,cosa,0,0], [0,0,1,0], [0,0,0,1] ]; return m; } function scale( sx, sy, sz ) { // R4 -> R4 return [ [sx,0,0,0], [0,sy,0,0], [0,0,sz,0], [0,0,0,1] ]; } function translate( tx, ty, tz ) { // R4 -> R4 return [ [1,0,0,tx], [0,1,0,ty], [0,0,1,tz], [0,0,0,1] ]; } function perspective ( f ) { // R4 -> R4 ( x=X, y=Y, z=Z, w=Z/f+1 ) return [ [1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,1/f,1] ]; } function product ( m1, m2 ) { // 4x4 matrix * 4x4 matrix -> 4x4 matrix var m = []; for (var i=0; i< 4; i++) { var mm = []; for (var j=0; j< 4; j++) { var s = 0; for (var k=0; k< 4; k++) { s += m1[i][k]*m2[k][j]; } mm[j] = s; } m[i] = mm; } return m; } function transform ( m, p0 ) { // apply m on a 4D point var p1 = []; for (var i=0; i< 4; i++) { var temp = 0; for (var j=0; j< 4; j++) temp += m[i][j]*p0[j]; p1[i] = temp; } return p1 // return a R4 point } function polyline ( t , f ) { var NEAR = -400, FAR = 500; var m = CAM; m = product( m, rotate( 'OX', t[0] ) ); m = product( m, rotate( 'OY', t[1] ) ); m = product( m, rotate( 'OZ', t[2] ) ); // console.log( t ) ; // m = product( m, scale( t[3], t[3], t[3] ) ); var tab = []; for (var i=0; i< f.length; i++) { var p = transform( m, f[i] ); tab[i] = [p[0]/p[3], p[1]/p[3], p[2]/p[3]]; } // draw only segments entirely in clipping planes for (var poly ='', i=0; i< tab.length-1; i++) { if ( tab[i][2] > NEAR && tab[i][2] < FAR && tab[i+1][2] > NEAR && tab[i+1][2] < FAR ) poly += 'M ' + tab[i][0] + ',' + tab[i][1] + ' ' + tab[i+1][0] + ',' + tab[i+1][1] + ' '; } return poly // SVG formated } // de casteljau var interpol = function(a,b,t) { return [ a[0]*(1-t)+b[0]*t, a[1]*(1-t)+b[1]*t, a[2]*(1-t)+b[2]*t, a[3]*(1-t)+b[3]*t ] }; var sub = function(arr,acc,t) { if (arr.length < 2) return acc; else { acc.push( interpol(arr[0],arr[1],t) ); return sub( arr.slice(1,),acc,t ) } }; var split = function(arr,acc,t,lr) { if (arr.length < 1) return acc else { if (lr) acc.push( arr[0] ) else acc.unshift( arr[arr.length-1] ) return split( sub(arr,[],t), acc, t, lr ) } }; var stretch = function(arr,t0,t1) { return split( split(arr,[],t0,false), [], t1, true) }; var blossom = function(arr,n) { if (n < 1) return arr else return [blossom( split(arr,[],0.5,true), n-1 ), blossom( split(arr,[],0.5,false), n-1 )] }; // PUBLIC FUNCTIONS return { init:init, polyline:polyline, stretch:stretch, blossom:blossom }; })(); // end W3D LAMBDATALK.DICT['W3D.init'] = function() { // {W3D.init3D ox oy oz sc pers} var args = LAMBDATALK.supertrim(arguments[0]).split(" "); return W3D.init( args[0], args[1], args[2], args[3], args[4] ) }; LAMBDATALK.DICT['W3D.polyline'] = function() { // {W3D.polyline [[...]] p} var args = LAMBDATALK.supertrim(arguments[0]).split(" "); var m = JSON.parse(args.shift()); var p = JSON.parse( args.join(" ")); return W3D.polyline(m,p) }; LAMBDATALK.DICT['W3D.stretch'] = function() { // {stretch t0 t1 [[...]]} var args = LAMBDATALK.supertrim(arguments[0]).split(" "); var t0 = parseFloat( args.shift()); var t1 = parseFloat( args.shift()); var arr = JSON.parse(args.join(" ")); return JSON.stringify(W3D.stretch(arr,t0,t1)) }; LAMBDATALK.DICT['W3D.blossom'] = function() { // {blossom n [[...]]} var args = LAMBDATALK.supertrim(arguments[0]).split(" "); var n = parseFloat(args.shift()); return "["+JSON.stringify(W3D.blossom( JSON.parse(args.join(" ")), n )) .replace( /\[{2,}/g, "[" ) .replace( /\]{2,}/g, "]" )+"]" }; °°} _h2 Next step _p Build {b pSurfaces & pVolumes} as in {a {@ href="http://lambdaway.free.fr/workshop/?view=pforms"}PFORMS}. _p {i alain marty 2020/11/18} ;; W3D library {{hide} {def parameter {lambda {:name :id :val :min :max :step} {center :name : {input {@ id=":id" type="text" value=":val" size="5" style="background:#444; color:#fff; border:0px solid"}} {input {@ type="range" min=":min" max=":max" value=":val" step=":step" style="width:110px; vertical-align:middle;" oninput="camera(':id', this.value)"}}}}} {def W3D.open {lambda {:w :h} svg {@ width=":w" height=":h" style="box-shadow:0 0 8px #000; background:#666; border:0px solid #fff;"}}} {def W3D.frame {lambda {:w :h} g {@ id="forms" transform="translate({/ :w 2},{/ :h 2})"}}} {def W3D.addForm {lambda {:w :col :m :points} {path {@ d="{W3D.polyline :m :points}" fill="transparent" stroke-width=":w" stroke=":col"}}}} } {script var camera = function(id,val) { var rox = (id === 'rox')? val : document.getElementById("rox").value; var roy = (id === 'roy')? val : document.getElementById("roy").value; var roz = (id === 'roz')? val : document.getElementById("roz").value; var sca = (id === 'scale')? val : document.getElementById("scale").value; document.getElementById(id).value = val; var newWorld = "{W3D " + rox + " " + roy + " " + roz + " " + sca + " " + " 500}"; document.getElementById('W3D').innerHTML = LAMBDATALK.eval_forms( newWorld ) }; var W3D = (function() { var CAM = []; // W3D global : 3D camera function init( rox,roy,roz,scal,pers ) { CAM = [ [1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1] ]; CAM = product( CAM, perspective( pers ) ); CAM = product( CAM, rotate( 'OX', rox ) ); CAM = product( CAM, rotate( 'OY', roy ) ); CAM = product( CAM, rotate( 'OZ', roz ) ); CAM = product( CAM, scale( scal, -scal, -scal ) ); } function rotate( axe, a ) { // R4 -> R4 var a = 2*Math.PI/360*a, cosa = Math.cos(a), sina = Math.sin(a), m = ''; if (axe == 'OX') m = [ [1,0,0,0], [0,cosa,-sina,0], [0,sina,cosa,0], [0,0,0,1] ]; if (axe == 'OY') m = [ [cosa,0,sina,0], [0,1,0,0], [-sina,0,cosa,0], [0,0,0,1] ]; if (axe == 'OZ') m = [ [cosa,-sina,0,0], [sina,cosa,0,0], [0,0,1,0], [0,0,0,1] ]; return m; } function scale( sx, sy, sz ) { // R4 -> R4 return [ [sx,0,0,0], [0,sy,0,0], [0,0,sz,0], [0,0,0,1] ]; } function translate( tx, ty, tz ) { // R4 -> R4 return [ [1,0,0,tx], [0,1,0,ty], [0,0,1,tz], [0,0,0,1] ]; } function perspective ( f ) { // R4 -> R4 ( x=X, y=Y, z=Z, w=Z/f+1 ) return [ [1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,1/f,1] ]; } function product ( m1, m2 ) { // 4x4 matrix * 4x4 matrix -> 4x4 matrix var m = []; for (var i=0; i< 4; i++) { var mm = []; for (var j=0; j< 4; j++) { var s = 0; for (var k=0; k< 4; k++) { s += m1[i][k]*m2[k][j]; } mm[j] = s; } m[i] = mm; } return m; } function transform ( m, p0 ) { // apply m on a 4D point var p1 = []; for (var i=0; i< 4; i++) { var temp = 0; for (var j=0; j< 4; j++) temp += m[i][j]*p0[j]; p1[i] = temp; } return p1 // return a R4 point } function polyline ( t , f ) { var NEAR = -400, FAR = 500; var m = CAM; m = product( m, rotate( 'OX', t[0] ) ); m = product( m, rotate( 'OY', t[1] ) ); m = product( m, rotate( 'OZ', t[2] ) ); // console.log( t ) ; // m = product( m, scale( t[3], t[3], t[3] ) ); var tab = []; for (var i=0; i< f.length; i++) { var p = transform( m, f[i] ); tab[i] = [p[0]/p[3], p[1]/p[3], p[2]/p[3]]; } // draw only segments entirely in clipping planes for (var poly ='', i=0; i< tab.length-1; i++) { if ( tab[i][2] > NEAR && tab[i][2] < FAR && tab[i+1][2] > NEAR && tab[i+1][2] < FAR ) poly += 'M ' + tab[i][0] + ',' + tab[i][1] + ' ' + tab[i+1][0] + ',' + tab[i+1][1] + ' '; } return poly // SVG formated } // de casteljau var interpol = function(a,b,t) { return [ a[0]*(1-t)+b[0]*t, a[1]*(1-t)+b[1]*t, a[2]*(1-t)+b[2]*t, a[3]*(1-t)+b[3]*t ] }; var sub = function(arr,acc,t) { if (arr.length < 2) return acc; else { acc.push( interpol(arr[0],arr[1],t) ); return sub( arr.slice(1,),acc,t ) } }; var split = function(arr,acc,t,lr) { if (arr.length < 1) return acc else { if (lr) acc.push( arr[0] ) else acc.unshift( arr[arr.length-1] ) return split( sub(arr,[],t), acc, t, lr ) } }; var stretch = function(arr,t0,t1) { return split( split(arr,[],t0,false), [], t1, true) }; var blossom = function(arr,n) { if (n < 1) return arr else return [blossom( split(arr,[],0.5,true), n-1 ), blossom( split(arr,[],0.5,false), n-1 )] }; // PUBLIC FUNCTIONS return { init:init, polyline:polyline, stretch:stretch, blossom:blossom }; })(); // end W3D LAMBDATALK.DICT['W3D.init'] = function() { // {W3D.init3D ox oy oz sc pers} var args = LAMBDATALK.supertrim(arguments[0]).split(" "); return W3D.init( args[0], args[1], args[2], args[3], args[4] ) }; LAMBDATALK.DICT['W3D.polyline'] = function() { // {W3D.polyline [[...]] p} var args = LAMBDATALK.supertrim(arguments[0]).split(" "); var m = JSON.parse(args.shift()); var p = JSON.parse( args.join(" ")); return W3D.polyline(m,p) }; LAMBDATALK.DICT['W3D.stretch'] = function() { // {stretch t0 t1 [[...]]} var args = LAMBDATALK.supertrim(arguments[0]).split(" "); var t0 = parseFloat( args.shift()); var t1 = parseFloat( args.shift()); var arr = JSON.parse(args.join(" ")); return JSON.stringify(W3D.stretch(arr,t0,t1)) }; LAMBDATALK.DICT['W3D.blossom'] = function() { // {blossom n [[...]]} var args = LAMBDATALK.supertrim(arguments[0]).split(" "); var n = parseFloat(args.shift()); return "["+JSON.stringify(W3D.blossom( JSON.parse(args.join(" ")), n )) .replace( /\[{2,}/g, "[" ) .replace( /\]{2,}/g, "]" )+"]" }; }
lambdaway v.20211111