Print a cube's vertices and its covering triangles

9

Output coordinates of the vertices of a cube. Then, output a list of twelve triangles that will cover the cube, each triangle being a list of three vertex-indexes, consistently oriented. Output must be an ASCII string of distinct decimal numbers. This golf has no input. Winner is the fewest characters, where the character set is Unicode.

For an example, consider a 1x1x1 cube cornered at 0,0,0. The eight vertices of the cube can be described by the following xyz coordinates on a 3d Cartesian grid:

x y z = (0,0,1) (1,0,1) (1,1,1) (0,1,1) (0,0,0) (1,0,0) (1,1,0) (0,1,0)

Each vertex can be given an index: x y z->index: 0 0 1->0, 1 0 1->1, 1 1 1->2, 0 1 1->3, 0 0 0->4, 1 0 0->5, 1 1 0->6, 0 1 0->7

Now consider the top face, vertexes indexed zero to three. The two covering triangles can be described by three indices each:

[0,1,2] [2,3,0]

Here is a picture of this top face, viewed from above the cube:

 3_____2
 |    /| 
 |   / |                  
 |  /  |
 | /   |
 0_____1                

And here is a view from an angle.

    3____2
   / __-/|
 0/_`__1 |
  |    | /6
  |____|/
 4     5

Note the orientation, or 'winding', of both of these triangles is 'counterclockwise' when viewed from 'outside' the cube directly looking at the face in question (imagine visiting each vertex as listed, it goes counterclockwise). Now imagine this done for all six sides of the cube.

vertices: (0,0,1) (1,0,1) (1,1,1) (0,1,1) (0,0,0) (1,0,0) (1,1,0) (0,1,0)
triangles as indices: [0,1,2], [2,3,0], [6,5,4], [4,7,6], 
  [5,2,1], [2,5,6], [0,3,4], [4,3,7], [2,6,3], [3,6,7], [0,4,1], [1,4,5]

You can output any size of cube located at any coordinates. You can number and order the vertex coordinates however you wish. Indices can be 0 based or 1 based. The triangle's orientation can be either clockwise or counterclockwise when viewed from outside the cube as long as it is consistent for all triangles.

The output can be formatted however you wish, as long as each ASCII decimal number is separated by at least one non-numeric ASCII character. For instance the above example could also be output as follows:

0 0 1 1 0 1 1 1 1 0 1 1 0 0 0 1 0 0 1 1 0 0 1 0 
0 1 2 2 3 0 6 5 4 4 7 6 5 2 1 2 5 6 0 3 4 4 3 7 2 6 3 3 6 7 0 4 1 1 4 5

This golf is inspired by various 3d graphics systems and formats, including OpenGL, OBJ, OFF, AMF, CGAL, etc. This golf is similar to the golf by Calvin's Hobbies named Output a Face on a Numbered Cube , the big difference being you need to output the x y z coordinates of the vertices yourself and output triangle indices. Thanks for reading.

Per user inspiration here is a "helper" validation program in python2 (non-golfy) that will print 'ok' or 'not ok' for test output data in variables vertstr and idxstr. It doesn't work perfectly... but it can catch some errors.

Edit: fixed typo in example and bugs in validation code.

    

#vertstr = '0 0 0 0 0 1 0 1 0 0 1 1 1 0 0 1 0 1 1 1 0 1 1 1'
#idxstr = '1 2 0 2 1 3 7 5 6 4 6 5 2 4 0 4 2 6 7 3 5 1 5 3 4 1 0 1 4 5 7 6 3 2 3 6'
vertstr = '0 0 1 1 0 1 1 1 1 0 1 1 0 0 0 1 0 0 1 1 0 0 1 0'
idxstr = '0 1 2 2 3 0 6 5 4 4 7 6 5 2 1 2 5 6 0 3 4 4 3 7 2 6 3 3 6 7 0 4 1 1 4 5'

class Vector:
    def __init__(self,v):
        self.x,self.y,self.z=v[0],v[1],v[2]
    def __add__(self,v):
        return Vector([self.x+v.x,self.y+v.y,self.z+v.z])
    def __sub__(self,v):
        return Vector([self.x-v.x,self.y-v.y,self.z-v.z])
    def __str__(self):
        return str(self.x)+','+str(self.y)+','+str(self.z)

def cross(v1,v2):
    x = v1.y*v2.z-v2.y*v1.z
    z = v1.x*v2.y-v2.x*v1.y
    y = v1.z*v2.x-v2.z*v1.x
    return Vector([x,y,z])

# http://mathforum.org/library/drmath/view/55343.html & http://sympy.org
def winding(v1,v2,v3,obs):
    x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4=v1.x,v1.y,v1.z,v2.x,v2.y,v2.z,v3.x,v3.y,v3.z,obs.x,obs.y,obs.z
    d =     x1*(y2*z3 - y2*z4 - y3*z2 + y3*z4 + y4*z2 - y4*z3) 
    d = d + y1*(-x2*z3 + x2*z4 + x3*z2 - x3*z4 - x4*z2 + x4*z3) 
    d = d + z1*(x2*y3 - x2*y4 - x3*y2 + x3*y4 + x4*y2 - x4*y3)
    d = d - x2*y3*z4 + x2*y4*z3 + x3*y2*z4 - x3*y4*z2 - x4*y2*z3 + x4*y3*z2 
    return d

def normals(v1,v2,v3):
    va = v2-v1
    vb = v3-v2
    vc = v1-v3
    n1 = cross(va,vb)
    n2 = cross(vb,vc)
    n3 = cross(vc,va)
    return [n1,n2,n3]


def triplify(str):
    nums,triples=[],[]
    for num in str.split(' '): nums+=[int(num)]
    for i in range(0,len(nums),3):
        triples += [[nums[i],nums[i+1],nums[i+2]]]
    return triples

verts = triplify(vertstr)
indices = triplify(idxstr)
nsum = Vector([0,0,0])
windsum = 0
xs,ys,zs=[],[],[]
for v in verts:
    xs+=[v[0]]
    ys+=[v[1]]
    zs+=[v[2]]
#print xs,ys,zs,len(xs)
center=Vector([float(sum(xs))/len(xs),float(sum(ys))/len(ys),float(sum(zs))/len(zs)])
for triangle in indices:
    v1 = Vector(verts[triangle[0]])
    v2 = Vector(verts[triangle[1]])
    v3 = Vector(verts[triangle[2]])
    norms = normals(v1,v2,v3)
    print v1,v2,v3,norms[0],norms[1],norms[2]
    for n in norms:
        nsum += n
    w = winding(v1,v2,v3,center)
    print 'winding',w
    if w<0: windsum-=1
    elif w>0: windsum+=1
if abs(windsum)==12: print 'winding ok'
else: print 'winding not ok'
if (nsum.x==0 and nsum.y==0 and nsum.z==0): print 'normal sum ok'
else: print 'normal sum not ok'

don bright

Posted 2015-10-19T16:30:31.583

Reputation: 1 189

1It's clear from the example, but just to make it perfectly unambiguous, you may want to mention that the indices are 0-based. This is not a given, since at least one of the formats you list as example (OBJ) uses 1-based indices. – Reto Koradi – 2015-10-19T16:37:33.030

That will work as well. I think one difficulty with this challenge is that it's moderately painful to verify the correctness of the output. You pretty much have to sketch a cube with the chose vertex ordering on a piece of paper, and manually validate all 12 triangles. Well, you could write a validation program. That might actually be another challenge idea... more difficult than this one, I think. – Reto Koradi – 2015-10-19T16:54:23.140

i really like that idea of another golf for a validator. i updated the example to give a full dataset. thanks again. – don bright – 2015-10-19T17:22:37.310

ok I added a very quick and dirty validation program that takes cross products of each pair of vectors in each triangle, adds them all, and if 0 says 'ok'. – don bright – 2015-10-20T00:20:00.173

Answers

1

Pyth, 18 characters

j`CM"⭧勛囃勦⾽仵ᶌﻘꚱ쥎➡˻ì

Same idea as my Haskell answer; prints:

[
1
1
1
1
1
,

2
1
2
1
1
...

Lynn

Posted 2015-10-19T16:30:31.583

Reputation: 55 648

i love that you used the same unicode string in 3 different languages – don bright – 2015-10-20T15:15:41.607

1What is this unicode magic? – RK. – 2015-10-20T20:29:21.687

2

CJam, 35 bytes

YZm*`3{[XY4]m<)\0+_:+1$f-+_@f+W%}%`

Try it online

The output is:

[[0 0 0] [0 0 1] [0 1 0] [0 1 1] [1 0 0] [1 0 1] [1 1 0] [1 1 1]][[1 2 0 2 1 3] [7 5 6 4 6 5] [2 4 0 4 2 6] [7 3 5 1 5 3] [4 1 0 1 4 5] [7 6 3 2 3 6]]

Triangle orientation is clockwise from the outside. I checked this manually, and it looks correct to me.

Explanation:

YZ      Push 2 and 3 on stack.
m*      Cartesian power, creates the coordinates of the 8 vertices.
`       Convert to string for output. Done with vertices.
3{      Start loop over 3 coordinate directions.
  [XY4]   Push [1 2 4], which are the vertex index offsets for the 3 directions.
  m<      Rotate by loop counter. So the remaining loop body will be executed once
          with [1 2 4], once with [2 4 1], once with [4 1 2].
  )       Pop off last offset. Will use this as index offset between the two
          parallel faces.
  \       Swap pair of remaining two offsets to top. These are the index offsets
          within the face.
  0+      Add a 0 to the list. These 3 indices define the first triangle.
  _:+     Calculate the sum. This is the vertex index of the opposite corner.
  1$      Copy first triangle to the top.
  f-      Subtract all indices from the index of the opposite corner, producing
          the second triangle of the face.
  +       Concatenate the indices of the two triangles, resulting in a list with
          the 6 vertex indices for the face.
  _       Copy the list.
  @       Bring the offset between the two faces to the top.
  f+      Add the offset to each index in the copied list.
  W%      Revert the order, resulting in the properly oriented list of the 6 vertex
          indices for the parallel face.
}%      End of loop over 3 coordinate directions.
`       Convert to string for output. Done with triangles.

Reto Koradi

Posted 2015-10-19T16:30:31.583

Reputation: 4 870

this is really cool. . . love the symmetry... – don bright – 2015-10-20T15:42:56.360

this is clearly the most fun answer, but i messed up my problem definition to have static description and "no input", so i have to keep the agreement and award the lowest character count, below (which is also a fun answer but in a different way), the Answer checkmark. thanks for participating. – don bright – 2015-11-22T22:31:36.623

1

Ruby, 98106

Fixed error spotted by Reto Koradi.

s=sprintf'%024b',342391
6.times{|i|t='15462315'[i,3];t+=t.reverse;t[1+i%2*3]='07'[i%2];s+=t}
p s.split(//)

Given that coordinates are required, the only corner numbering scheme that made sense seemed to be the one where each corner is the binary representation of its coordinates. That's quite different from the linked question, where various different numbering schemes were tried. In the end I decided to print the coordinates with a dirty hardcode: s is initialised to the string version of 24-bit number000001010011100101110111 whose decimal representation is 342391. Actually with this method of printing coordinates, the numbering of the vertices is flexible, so I may do another answer.

Going round the equator of the cube, we find the vertices 1,5,4,6,2,3 and we can define one triangle for each face from any 3 consecutive numbers on this list (wrapping back to the beginning at the end.) The other triangle on each face is defined by reversing the digits, and substituting the middle digit with 0 or 7 as appropriate.

This gives all the required output, but without any separating characters. To achieve that, I simply convert to an array of characters and print the array, like this (linebreaks inserted to prevent scrolling):

["0", "0", "0", "0", "0", "1", "0", "1", "0", "0", "1", "1", "1", "0", "0",
 "1", "0", "1", "1", "1", "0", "1", "1", "1", "1", "0", "4", "4", "5", "1",
 "5", "4", "6", "6", "7", "5", "4", "0", "2", "2", "6", "4", "6", "2", "3",
 "3", "7", "6", "2", "0", "1", "1", "3", "2", "3", "1", "5", "5", "7", "3"]

Level River St

Posted 2015-10-19T16:30:31.583

Reputation: 22 049

Are you sure that the winding order is consistent? Based on my sketch, 1, 5, 4 is CCW, 5, 4, 6 is CW. – Reto Koradi – 2015-10-20T06:26:08.610

@RetoKoradi fixed at a cost of 8 bytes. Thanks. Also, I've realised I may do better with a different numbering scheme. – Level River St – 2015-10-20T10:20:17.357

1

JavaScript (ES6) 78

alert([...'1010011100101110111:120213756465240426735153410145763236'].join` `)

Sorry but I really don't understand these challenges with no input.

edc65

Posted 2015-10-19T16:30:31.583

Reputation: 31 086

sorry, it was my first golf question. i guess it is too late to change it now... – don bright – 2015-10-20T15:37:44.027

Better next time. You have my vote anyway. – edc65 – 2015-10-20T16:11:28.200

1

Haskell, 38 characters

f=mapM(mapM print.show)"⭧勛囃勦⾽仵ᶌﻘꚱ쥎➡˻ì"

Prints the right numbers, separated by a whole lot of junk:

'\''
'\\'
'1'
'1'
'1'
'1'
'1'
'\''
'\''
'\\'
'2'
'1'
'2'
'1'
'1'
...

The cube's diagonal is from (1, 1, 1) to (2, 2, 2).

Lynn

Posted 2015-10-19T16:30:31.583

Reputation: 55 648

1

CJam, 20 characters

"⭧勛囃勦⾽仵ᶌﻘꚱ쥎➡˻ì":isS*

Same idea as my Haskell answer; prints:

1 1 1 1 1 2 1 2 1 1 2 2 2 1 1 2 1 2 2 2 1 2 2 2 1 2 0 2 1 3 7 5 6 4 6 5 2 4 0 4 2 6 7 3 5 1 5 3 4 1 0 1 4 5 7 6 3 2 3 6

Lynn

Posted 2015-10-19T16:30:31.583

Reputation: 55 648

1

Ruby, Rev 1 62

29.downto(0){|c|p c>5?73888640>>c&1:[c,c^1,c|6,c|6,(c+3)%6,c]}

Got rid of the c-6 by multiplying the magic number by 64.

The assignment of coordinates is below. It's odd that I assigned 100 to number 1. I could have saved a byte in rev 0 by exchanging the axes and assigning 001 to number 1. The reason it was that way was because originally I had up counting in the loop, which would have meant I had to put everything in reverse in the magic string. Anyway, with the change I've made now, there's no additional saving to be made, so i'll leave the coordinates as they are

Cube rotated with 0163 face at back
Top layer from above
01   000 100
74   010 110    
Bottom layer from above
36   001 101   
25   011 111

Ruby, Rev 0 63

29.downto(0){|c|p c>5?1154510>>c-6&1:[c,c^1,c|6,c|6,(c+3)%6,c]}

Using hardcoding of the coordinates data to give flexibility in picking the corners. There are 54 digits in the output, meaning that the naive solution would have 63-54=9 bytes available for code. As I can't think of a way to insert spaces in 9 bytes, I believe this is shorter than the naive solution.

Numbering scheme (adapted from my Ruby answer to the linked question https://codegolf.stackexchange.com/a/48867/15599)

4---7
|  /|
| / |
|/  |
1---0---7
|  /|  /|
| / | / |
|/  |/  |
6---3---2---7
    |  /|  /|
    | / | / |
    |/  |/  |
    6---5---4
        |  /|
        | / |
        |/  |
        6---1

Output

0
0
0
1
0
0
0
1
1
0
0
1
1
1
0
1
1
1
0
0
1
1
1
0
[5, 4, 7, 7, 2, 5]
[4, 5, 6, 6, 1, 4]
[3, 2, 7, 7, 0, 3]
[2, 3, 6, 6, 5, 2]
[1, 0, 7, 7, 4, 1]
[0, 1, 6, 6, 3, 0]

Level River St

Posted 2015-10-19T16:30:31.583

Reputation: 22 049

I really like the incorporation of @Runer112 's method – don bright – 2015-10-20T15:35:46.207

@donbright I was the first one to think of putting the first 6 vertices on the equator and the last 2 on the poles in that previous question, which is why my C answer is the most popular answer. I had the 6 vertices in sequential order. Runer112 deserves some credit for his reordering of the 6 vertices on the equator. I had to modify the face order for Ruby on the previous question, but the vertex order is indeed identical to Runer112's. Phinotphi's alternative reordering of the 6 vertices on the equator would have given me equal length on the previous question, but would be longer on this one – Level River St – 2015-10-20T20:37:16.903

wow awesome... thanks for the detailed explanation... very interesting. i should have allowed input then it would have been a better challenge. – don bright – 2015-10-21T01:53:52.450