View in color | View documentation | License |
Download script | History | Other scripts by: crazyaxe |
30-Apr 6:37 UTC
[0.082] 34.669k
[0.082] 34.669k
r3d2.rREBOL [
Title: "r3D"
Author: [ "Massimiliano Vessi" "Andrew Hoadley" "François Jouen"]
Email: %maxint--tiscali--it
Date: 09-Oct-2012
version: 2.2.6
file: %r3d2.r
Purpose: {"3D Library, you can use also images.
Look a the end of the file to see how are made 3D model.}
;following data are for www.rebol.org library
;you can find a lot of rebol script there
library: [
level: 'intermidiate
platform: 'all
type: [ tool]
domain: [animation external-library graphics]
tested-under: [windows linux]
support: none
license: [gpl]
see-also: none
]
]
; Copyright (c) 2006, Andrew Hoadley
; Permission is hereby granted, free of charge, to any person obtaining a copy of this software
; and associated documentation files (the "Software"), to deal in the Software without restriction,
; including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
; and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
; subject to the following conditions:
; The above copyright notice and this permission notice shall be included in all copies or substantial
; portions of the Software.
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
; NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
; OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
; ---------------------------------------------------------
; Contents of r3d-matrix.r - matrix and vector library
;
; create different types of transformations
r3d-identity: [
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
]
r3d-perspective: func [
"Create a perspective matrix with a vanishing point d units from the camera"
d [number!] "d is the distance to the vanishing point - don't set it to zero !!"
][reduce [
1 0 0 0
0 1 0 0
0 0 0 0
0 0 1 / d 1
]
];end of r3d-perspective
r3d-translate: func [
"Create a translation matrix"
X [number!] "translate X units along the x axis"
Y [number!] "translate Y units along the y axis"
Z [number!] "translate Z units along the z axis"
][ reduce [
1 0 0 X
0 1 0 Y
0 0 1 Z
0 0 0 1
]
]; end of r3d-translate
r3d-scale: func [
"Create a scale matrix"
X [number!] "scale object by a factor of X along the x axis"
Y [number!] "scale object by a factor of Y along the y axis"
Z [number!] "scale object by a factor of Y along the z axis"
][ reduce [
X 0 0 0
0 Y 0 0
0 0 Z 0
0 0 0 1
]
]; end of r3d-scale
r3d-rotateX: func [
X [number!]
][ ; calculate once and store the sin and cos values
sineX: sine X
cosineX: cosine X
reduce [
1 0 0 0
0 cosineX (- sineX) 0
0 sineX cosineX 0
0 0 0 1
]
]; end of r3d-rotateX
r3d-rotateY: func [
Y [number!]
][ ; calculate once and store the sin and cos values
sineY: sine Y
cosineY: cosine Y
reduce [
cosineY 0 sineY 0
0 1 0 0
(- sineY) 0 cosineY 0
0 0 0 1
]
]; end of r3d-rotateY
r3d-rotateZ: func [
Z [number!]
][ ; calculate once and store the sin and cos values
sineZ: sine Z
cosineZ: cosine Z
reduce [
cosineZ (- sineZ) 0 0
sineZ cosineZ 0 0
0 0 1 0
0 0 0 1
]
]; end of r3d-rotateZ
r3d-face-direction: func [
DOF [block!] "Vector3 specifying direction of flight"
up [block!] "Up vector may be in any direction except parallel to DOF"
/local xaxis yaxis zaxis
][
zaxis: r3d-normalise DOF
xaxis: r3d-normalise r3d-crossproduct up dOF
yaxis: r3d-normalise r3d-crossproduct DOF xaxis
reduce [
xaxis/1 yaxis/1 zaxis/1 0
xaxis/2 yaxis/2 zaxis/2 0
xaxis/3 yaxis/3 zaxis/3 0
0 0 0 1
]
];end of r3d-face-direction
r3d-position-object: func [
object_position [block!] ; all 3 parameters are 3 dimensional vectors
position_to_look_at [block!]
up [block!]
/local DOF
][
DOF: r3d-Subtract position_to_look_at object_position
zaxis: r3d-normalise DOF
xaxis: r3d-normalise r3d-crossproduct DOF up
yaxis: r3d-normalise r3d-crossproduct DOF xaxis
reduce [
xaxis/1 yaxis/1 zaxis/1 object_position/1
xaxis/2 yaxis/2 zaxis/2 object_position/2
xaxis/3 yaxis/3 zaxis/3 object_position/3
0 0 0 1
]
]; endo of r3d-position-object
r3d-m4xm4: func [
"Matrix product between two 4x4 matrices"
m1 [block!] "First matrix, length must be 16 (4x4)"
m2 [block!] "Second matrix, length must be 16 (4x4)"
/local result
][
result: copy []
foreach [a b c d] m1 [
append result reduce [
(a * m2/1) + (b * m2/5) + (c * m2/9) + (d * m2/13)
(a * m2/2) + (b * m2/6) + (c * m2/10) + (d * m2/14)
(a * m2/3) + (b * m2/7) + (c * m2/11) + (d * m2/15)
(a * m2/4) + (b * m2/8) + (c * m2/12) + (d * m2/16)
]
]
result
]; end of r3d-m4xm4
r3d-m4xv3: func [
"Matrix product between a 4x4 matrix and a 4x1 ([A B C 1]) vector. This function will append the last 1 to the vector 3x1"
m [block!] "A 4x4 metrix (length 16)"
v [block!] " A 3x1 vector, this function will tranform it in a 4x1 appnding a 1"
/local result
][
tempres: copy []
result: copy []
foreach [a b c d] m [
append tempres reduce [
(a * v/1) + (b * v/2) + (c * v/3) + d
]
]
; create 1/w so we only divide once and then multiply by a fraction (faster)
inv-w: reduce (1 / tempres/4)
append result reduce tempres/1 * inv-w
append result reduce tempres/2 * inv-w
append result reduce tempres/3 * inv-w
append result 1
return result
];end of r3d-m4xv3
r3d-m4xv3-array: func [
"Thi function return the same block of vertices, multiplied for the transformation matrix"
vertices [block!] "This must be a block of vertices coordinates, like [ [ 1 2 3] [-2 5.2 4] ]"
m4 [block!] "This is a 4x4 matrix"
/local result vertex
][
result: copy []
foreach vertex vertices [
append result reduce [ r3d-m4xv3 m4 vertex ]
]
return result
]; end of r3d-m4xv3-array
;Is this to compose all translation and rotation?
r3d-compose-m4: func [
"Take a block containing 4x4 matrices and moltiplicate each other obtaining a single 4x4 matrix"
matrixlist [block!] ; a list of at least 1 4x4 matrix
/local result
][
cm4-len: length? matrixlist
; if there are no entries return the identity matrix
if cm4-len = 0 [ return r3d-identity ]
; initialise the result with the first entry in the list
result: copy matrixlist/1
; if there is one entry return that entry
if cm4-len = 1 [ return result ]
; for each rentry past the first
for cm4-i 2 cm4-len 1 [
;multiply the previous result by the next element
result: r3d-m4xm4 pick matrixlist cm4-i result
]
return result
]; end of r3d-compose-m4
r3d-transpose-m4: func [
"Transpose a 4x4 matrix"
m [block!] "Matrix to transpose (4x4)"
/local result
][
; transpose a 4x4 matrix
result: reduce [ m/1 m/5 m/9 m/13 m/2 m/6 m/10 m/14 m/3 m/7 m/11 m/15 m/4 m/8 m/12 m/16]
]
r3d-inverse-m4: func [
"Inverse of a 4x4 matrix"
m [block!] "Matrix 4x4"
/local result pairs m4inv-res det
][
; ok here we go...
; calculate pairs for first 8 elements
pairs: reduce [
m/11 * m/16
m/12 * m/15
m/10 * m/16
m/12 * m/14
m/10 * m/15
m/11 * m/14
m/9 * m/16
m/12 * m/13
m/9 * m/15
m/11 * m/13
m/9 * m/14
m/10 * m/13
]
; calculate first 8 elements
m4inv-res: reduce [
((pairs/1 * m/6) + (pairs/4 * m/7) + (pairs/5 * m/8)) -
((pairs/2 * m/6) + (pairs/3 * m/7) + (pairs/6 * m/8))
((pairs/2 * m/5) + (pairs/7 * m/7) + (pairs/10 * m/8)) -
((pairs/1 * m/5) + (pairs/8 * m/7) + (pairs/9 * m/8))
((pairs/3 * m/5) + (pairs/8 * m/6) + (pairs/11 * m/8)) -
((pairs/4 * m/5) + (pairs/7 * m/6) + (pairs/12 * m/8))
((pairs/6 * m/5) + (pairs/9 * m/6) + (pairs/12 * m/7)) -
((pairs/5 * m/5) + (pairs/10 * m/6) + (pairs/11 * m/7))
((pairs/2 * m/2) + (pairs/3 * m/3) + (pairs/6 * m/4)) -
((pairs/1 * m/2) + (pairs/4 * m/3) + (pairs/5 * m/4))
((pairs/1 * m/1) + (pairs/8 * m/3) + (pairs/9 * m/4)) -
((pairs/2 * m/1) + (pairs/7 * m/3) + (pairs/10 * m/4))
((pairs/4 * m/1) + (pairs/7 * m/2) + (pairs/12 * m/4)) -
((pairs/3 * m/1) + (pairs/8 * m/2) + (pairs/11 * m/4))
((pairs/5 * m/1) + (pairs/10 * m/2) + (pairs/11 * m/3)) -
((pairs/6 * m/1) + (pairs/9 * m/2) + (pairs/12 * m/3))
]
; calculate pairs for second 8 elements
pairs: reduce [
m/3 * m/8
m/4 * m/7
m/2 * m/8
m/4 * m/6
m/2 * m/7
m/3 * m/6
m/1 * m/8
m/4 * m/5
m/1 * m/7
m/3 * m/5
m/1 * m/6
m/2 * m/5
]
; calculate second 8 elements (cofactors)
m4inv-res: append m4inv-res reduce [
((pairs/1 * m/14) + (pairs/4 * m/15) + (pairs/5 * m/16)) -
((pairs/2 * m/14) + (pairs/3 * m/15) + (pairs/6 * m/16))
((pairs/2 * m/13) + (pairs/7 * m/15) + (pairs/10 * m/16)) -
((pairs/1 * m/13) + (pairs/8 * m/15) + (pairs/9 * m/16))
((pairs/3 * m/13) + (pairs/8 * m/14) + (pairs/11 * m/16)) -
((pairs/4 * m/13) + (pairs/7 * m/14) + (pairs/12 * m/16))
((pairs/6 * m/13) + (pairs/9 * m/14) + (pairs/12 * m/15)) -
((pairs/5 * m/13) + (pairs/10 * m/14) + (pairs/11 * m/15))
((pairs/3 * m/11) + (pairs/6 * m/12) + (pairs/2 * m/10)) -
((pairs/5 * m/12) + (pairs/1 * m/10) + (pairs/4 * m/11))
((pairs/9 * m/12) + (pairs/1 * m/9) + (pairs/8 * m/11)) -
((pairs/7 * m/11) + (pairs/10 * m/12) + (pairs/2 * m/9))
((pairs/7 * m/10) + (pairs/12 * m/12) + (pairs/4 * m/9)) -
((pairs/11 * m/12) + (pairs/3 * m/9) + (pairs/8 * m/10))
((pairs/11 * m/11) + (pairs/5 * m/9) + (pairs/10 * m/10)) -
((pairs/9 * m/10) + (pairs/12 * m/11) + (pairs/6 * m/9))
]
; calculate determinate
det: (m/1 * m4inv-res/1) +
(m/2 * m4inv-res/2) +
(m/3 * m4inv-res/3) +
(m/4 * m4inv-res/4)
; invert to avoid doing multiple divisions
det: 1.0 / det
result: copy []
foreach x m4inv-res [ append result reduce ( x * det ) ]
return r3d-transpose-m4 result
] ; end of r3d-inverse-m4
r3d-dotproduct: func [
"Returns the dot product (a number) between two 3x1 vector"
v1 [block!] "Vector (3x1)"
v2 [block!] "Vector (3x1)"
/local result
][result: reduce (v1/1 * v2/1) + (v1/2 * v2/2) + (v1/3 * v2/3)]
r3d-crossproduct: func [
"Returns a cross product (vector, 3x1) between 2 vectors (3x1)"
v1 [block!] "3x1 vector"
v2 [block!] "3x1 vector"
/local result
][
result: reduce [
(v1/2 * v2/3) - (v1/3 * v2/2)
(v1/3 * v2/1) - (v1/1 * v2/3)
(v1/1 * v2/2) - (v1/2 * v2/1)
]
return result
]
r3d-length: func [
"Returns the disctance between origin and a point (3x1)"
v [block!] "Point coordinate (3x1)"
/local result
][
result: reduce square-root ( (v/1 * v/1) + (v/2 * v/2) + (v/3 * v/3) )
]
r3d-Add: func [
"Add a to b - a and b can be either matrices or vectors but types must match"
a [block!]
b [block!]
/local item result
][
result: copy []
for item 1 length? a 1 [ append result reduce ( pick a item ) + ( pick b item ) ]
return result
]
r3d-Subtract: func [
"Subtract b from a - a and b can be either matrices or vectors but types must match"
a [block!]
b [block!]
/local item result
][
result: copy []
for item 1 length? a 1 [ append result reduce ( pick a item ) - ( pick b item ) ]
return result
]
r3d-Multiply: func [
"Multiply a by n - a and b can be either a matrix or vector, n is decimal"
a [block!]
n [decimal!]
/local item result
][
result: copy []
for item 1 length? a 1 [ append result reduce ( pick a item ) * n ]
return result
]
r3d-Divide: func [
"Divide a by n - a and b can be either a matrix or vector, n is non-zero decimal"
a [block!]
n [decimal!]
/local item result div
][
result: copy []
if n = 0.0 [ Print "Divide by zero attempted" return a ]
div: 1.0 / n
for item 1 length? a 1 [ append result reduce ( pick a item ) * div ]
return result
]
r3d-normalise: func [
"Normalize a vector, its module becomes 1"
v [block!] "Vector 3x1"
/local result
][
; try to normalise a zero vector and you will get a zero vector back
len: r3d-length v
either ( len = 0 ) [
result: [ 0.0 0.0 0.0 ]
] [
result: reduce [
v/1 / len
v/2 / len
v/3 / len
]
]
return result
]
;Debug functions?
r3d-print-m4: func [
m [block!]
][
foreach [ a b c d ] m [ print [ a b c d ] ]
print []
]
r3d-print-v3: func [
v [block!]
][
foreach elem v [ print elem ]
print []
]
;-------------------------------------------------
;
; contents of r3d-engine.r - r3d render engine
;
Render: func [
"Main function that render 3D objects creating a VID DRAW"
world [block!] "List of all object to render and their position"
camera [block!] "Camera position, where it look and where is the UP"
projection [block!] "Type of projection to use, 4x4 matrix, in doubt use: r3d-perspective 250. You can create funny distortions..."
canvasSize [pair!] "Window size of the rendering"
/no-cull
/local result transvert trans2d model modelworld triangles
][
result: copy []
triangles: copy []
cameraInverse: r3d-inverse-m4 camera
foreach r3dobject world [
model: r3dObject/1
modelworld: r3dobject/2
objcolor: r3dObject/3
ModelCamera: r3d-m4xm4 cameraInverse modelWorld
; transform the vertices into 3d space relative to the camera
transVert: r3d-m4xv3-array model/1 modelcamera
faces: model/2
; faceinfo contains 2 blocks of n entries, a) face normals and b) furthest Z offset
faceInfo: r3d-CalculateFaceNormals transVert faces
; transform the vertices again using the projection matrix
trans2d: r3d-m4xv3-array transvert projection
;culling or not back face culling?
either no-cull [append triangles r3d-Render2dTriangles-simple/no-cull trans2d faces faceInfo canvasSize objcolor
][append triangles r3d-Render2dTriangles-simple trans2d faces faceInfo canvasSize objcolor]
]
; depth sort
triangles: sort/reverse triangles
;probe triangles
foreach triangle triangles [
fillcolour: last triangle
temptriang: copy triangle
remove temptriang
reverse temptriang
remove temptriang
reverse temptriang
either tuple? fillcolour [
; face is filled with a colour
append result reduce ['pen fillcolour 'fill-pen fillcolour ]
append result 'polygon
foreach item temptriang [
append result item
]
][ ;image case
either (length? temptriang) = 4 [
append result 'image
foreach item temptriang [
append result item
]
append result fillcolour
][
append result reduce ['pen none 'fill-pen fillcolour ]
append result 'polygon
foreach item temptriang [
append result item
]
]
]
;append result 'polygon
;append result triangle/2
;append result triangle/3
;append result triangle/4
]
;print form result
return result
]
r3d-CalculateFaceNormals: func [
vertices [block!]
faces [block!]
/local result depthvals v1 v2 v3 vtmp1 vtmp2 vcp largest
][
result: copy []
depthvals: copy []
foreach face faces [
; get the vertices pointed to by each index in the face
v1: pick vertices face/1
v2: pick vertices face/2
v3: pick vertices face/3
; get face normal
vtmp1: r3d-subtract v2 v1
vtmp2: r3d-subtract v3 v2
vcp: r3d-crossproduct vtmp1 vtmp2
vcp: r3d-normalise vcp
append/only result vcp
; get furthest z co-ord
largest: -10000.0
if v1/3 > largest [ largest: v1/3 ]
if v2/3 > largest [ largest: v2/3 ]
if v3/3 > largest [ largest: v3/3 ]
append depthvals largest
]
reduce [ result depthvals ]
]
r3d-Render2dTriangles-Simple: func [
transformedVertices [block!]
faces [block!]
faceInfo [block!]
canvasSize [pair!]
objColor [tuple! image!]
/no-cull
/local result temptriangle v face index origin count facez
][
result: copy []
; todo - accurate lighting
; determine the origin
origin: (canvasSize * 0.5)
faceNormals: faceInfo/1
depthvals: faceinfo/2
count: 1
foreach face faces [
; check if this face needs to be backface culled
facenormal: pick faceNormals count
;print ["facenormal: " facenormal]
depthval: pick depthvals count
;print ["depthval: " depthval]
count: count + 1
faceZ: facenormal/3 ;backface culling (removing back faces)
if no-cull [faceZ: negate abs facez ] ;max
if faceZ <= 0 [
; make the depthval the first entry in the block so that the block will be depth sorted by this value
temptriangle: copy []
append temptriangle depthval
foreach index face [
; get the vertex pointed to by this index
v: pick transformedVertices index
append temptriangle reduce ( origin + as-pair v/1 v/2 )
]
; todo lighting
facez: 0.1 - facez
either tuple? objColor [
append temptriangle ( objColor * facez ) ;if it's a color
][
facez2: to-integer ( facez * facez * 150)
append temptriangle to-image layout/tight compose/deep [image (objColor) effect [luma (facez2)]] ;else it's an image
]
append/only result temptriangle
]
]
result
]
r3d-Load-OFF: func [
"Load 3D OFF files, and create the correspective model"
file [file!]
/local bloc result verts faces numVerts numFaces numEdges temp_bl maxx minn
] [
bloc: read/lines file
; first of all remove empty lines that can be found in old OFF files
remove-each value bloc [empty? value]
if not found? find bloc/1 "OFF" [Alert "Block is not an OFF file" return none exit]
fileType: bloc/1
bloc: next bloc ; next line
;process possible header comment lines starting with #
while [bloc/1/1 = #"#" ] [bloc: next bloc]
;now we have the number of vertexes
numVerts: to-integer first parse bloc/1 none
numfaces: to-integer second parse bloc/1 none
;do we need it?
numedges: to-integer third parse bloc/1 none
result: copy []
verts: copy []
faces: copy []
maxx: 0
minn: 0
loop numVerts [
temp_bl: copy []
bloc: next bloc ; next line
line: parse bloc/1 none
foreach item line [
temp: to-decimal item
if temp > maxx [maxx: temp]
if temp < minn [minn: temp]
append temp_bl temp
]
append/only verts temp_bl
]
append/only result verts ; the first part of the model is done
loop numfaces [
temp_bl: copy []
bloc: next bloc ; next line
line: parse bloc/1 none
nv: to-integer line/1 ;number of vertexes to add
loop nv [
line: next line
;off models start to counter from zero!
append temp_bl ((to-integer line/1) + 1)
]
append/only faces temp_bl
]
append/only result faces ; the second part of the model is done
append result (maxx - minn) ;this give an hit for scaling the model
result
]
;----------------------------------------
;
; Load the base model
;
;
; Note: R3d supports loading the .OFF file format through the r3d-load-off function
;
;How to create base model:
;all model are made of triangles, you must give point and triangles to show
;a model is a block with inside 2 blocks: vertices and faces
;vertices contains vertices coordinates [x y z]
;faces set the triangle to show
;so [ 1 2 3] means to fill a triangle using point 1, 2 and 3
;THE ORDER OF THE POINTS IN FACE DESCRIPTION IS VERY IMPORTANT
; [1 2 3] IS DIFFERENT THAN [ 3 2 1 ]
;USE faces - anticlockwise winding
cube-model: [
; vertices
[
[ 1 1 0 ] ;point 1
[ 1 -1 0 ] ;point 2
[ -1 -1 0 ] ;point 3 and so on...
[ -1 1 0 ]
[ 1 1 1 ]
[ 1 -1 1 ]
[ -1 -1 1 ]
[ -1 1 1 ]
]
; faces - anticlockwise winding
[
[ 1 5 6 ]
[ 1 6 2 ]
[ 2 6 7 ]
[ 2 7 3 ]
[ 3 7 8 ]
[ 3 8 4 ]
[ 4 8 5 ]
[ 4 5 1 ]
[ 8 7 6 ]
[ 8 6 5 ]
[ 1 2 3 ]
[ 1 3 4 ]
]
]
cube2-model: [
; vertices
[
[ 0 0 0 ] ;point 1
[ 1 0 0 ] ;point 2
[ 1 1 0 ] ;point 3 and so on...
[ 0 1 0 ]
[ 0 0 1 ] ;point 5
[ 1 0 1 ] ;point 6
[ 1 1 1 ] ;point 7 and so on...
[ 0 1 1 ]
]
; faces - anticlockwise winding
[
[ 4 3 2 1] ;just six face square, instead of 12 tirangles
[ 5 6 7 8 ]
[ 1 5 8 4 ]
[ 1 2 6 5]
[ 2 3 7 6 ]
[ 8 7 3 4 ]
]
]
pyramid-model: [
;vertices
[
[1 0.87 2] ;1
[0 0 0] ;2
[2 0 0] ;3
[1 1.73 0] ;4
]
; faces
[
[4 2 1]
[4 3 2]
[1 3 4]
[ 1 2 3]
]
]
square-pyramid-model: [
;vertices
[
[1 1 2] ;1
[0 0 0] ;2
[2 0 0] ;3
[2 2 0] ;4
[0 2 0] ; 5
]
; faces
[
[1 5 2 ]
[1 2 3]
[1 3 4]
[1 4 5]
[3 2 5]
[3 5 4]
]
]
wall-model: [
;vertices
[
[0 0 0] ;1
[1 0 0] ;2
[1 0 1 ] ;3
[0 0 1 ] ;4
]
; faces
[
[3 4 1 2 ]
]
]
prysm-8-model: [
;vertices
[
[-0.414 -1 0] ;1
[0.414 -1 0] ;2
[1 -0.414 0 ] ;3
[1 0.414 0 ] ;4
[0.414 1 0] ;5
[-0.414 1 0] ;6
[-1 0.414 0 ] ;7
[-1 -0.414 0 ] ;8
[-0.414 -1 1] ;9
[0.414 -1 1] ;10
[1 -0.414 1 ] ;11
[1 0.414 1 ] ;12
[0.414 1 1] ;13
[-0.414 1 1] ;14
[-1 0.414 1 ] ;15
[-1 -0.414 1 ] ;16
]
; faces
[
[8 7 6 5 4 3 2 1 ]
[9 10 11 12 13 14 15 16]
[1 2 10 9]
[2 3 11 10]
[3 4 12 11]
[4 5 13 12]
[5 6 14 13]
[6 7 15 14]
[7 8 16 15]
[8 1 9 16]
]
] Notes
|