Message Boards Message Boards

Converting chess OBJ files to voxels

Posted 3 years ago

enter image description here

In another recent thread we announced the existence of voxel chess pieces originally painted in MagicaVoxel, but could not access them in Mathematica because of a fail case involving ".vox" file format. The purpose of this memo is to show how files exported to OBJ can be converted back to voxels, and to give away a few more free assets.

Pull down the OBJ files from github, then import and print:

PieceData = Import["~/mathfun/" <> # <> ".obj"] & /@ {"pawn", "rook", "knight", 
    "bishop", "queen", "king"}; 
GraphicsGrid[ Partition[Show[Region@#, 
     Graphics3D[Arrow[{{0, 0, 0}, 6 #}] & /@ IdentityMatrix[3]],
     ViewVertical -> {0, 1, 0}, ViewPoint -> {2, 0, 2},
     PlotRange -> {{-6, 6}, {-1, 20}, {-6, 6}}] & /@ PieceData, 3]]

Chess Regions

Validation data

RegionMember[Region[PieceData[[6]]], {0, 0, 0}]
RegionMember[Region[PieceData[[6]]], {0, 1, 0}]
RegionDimension@Region[PieceData[[6]]]
SolidRegionQ@Region[PieceData[[6]]]

Out[]:= True
Out[]:= False
Out[]:= 2
Out[]:= False

We would rather that this output read T,T,3,T, and are unaware whether or not region management is built out to this capability (?). That leaves us the task of identifying boundaries, surface normals, and putting voxel blocks in place, hopefully with color. Assuming the OBJ file is consistent with a VOX decomposition, we can use preexisting region functions to identify control points on the two-dimensional surface:

BoundaryPoints[GeoOBJ_] := With[{SlabPts = Flatten[
     Table[{i/2, j/2, k/2}, {i, -6, 6}, {j, 0, 32}, {k, -6, 6}], 2]},
  Select[SlabPts, Element[#, Region[GeoOBJ]] &]]  

In this case, we know the size of the slab, the origin of coordinates, and the length of the line element, so we don't need too much automation or corner finding. There are at least two good reasons for using halves in discretization of the slab, but you can think of those for yourself.

To get out square facets, we look for nearest neighbors and next nearest neighbors in the sub lattice found by intersection with whichever GeoOBJ.

ListEdges[BoundaryPts_] :=  With[{EdgeSet = 
    Edge[#, Nearest[Complement[BoundaryPts, {#}], #]] & /@ 
     BoundaryPts},  {EdgeSet,  Edge[#[[1]], 
      Nearest[Complement[ BoundaryPts, # /. 
         Edge[x_, y_] :> Append[y, x]], #[[1]] ] ] & /@ EdgeSet}]

Facet[pt_, nNeighbors_, nnNeighbors_] := MapIndexed[
  ReplaceAll[ Flatten[Position[#1, 1/2, 1]], {{x_Integer, y_Integer} :> 
      Polygon[ {pt, nNeighbors[[x]], nnNeighbors[[#2[[1]] ]], 
        nNeighbors[[y]]  }], _ -> {}}] &,
  Outer[EuclideanDistance, nnNeighbors, nNeighbors, 1]]

BoundaryDiscretize[GeoOBJ_] :=  Union[Flatten[
 MapThread[Facet[#1[[1]], #1[[2]], #2[[2]]] &,
     ListEdges[BoundaryPoints[GeoOBJ ]]  ]]];

AbsoluteTiming[AllFacets = BoundaryDiscretize[#] & /@ PieceData;]
Out[]={48.1591, Null}

It seems to take too long, but we are not yet at the finalization stage of needing to optimize. Actually, we have just started (as C.H. was saying yesterday, sort of). Printing Again:

GraphicsGrid[Partition[Show[Graphics3D[#], 
     Graphics3D[Arrow[{{0, 0, 0}, 6 #}] & /@ IdentityMatrix[3]],
     ViewVertical -> {0, 1, 0}, ViewPoint -> {2, 0, 2}, Boxed -> False,
     PlotRange -> {{-6, 6}, {-1, 20}, {-6, 6}}] & /@ AllFacets, 3], 
 ImageSize -> 500]

Faceted Chess Pieces

These should be relatively easy to color, facet by facet, except that we may still have duplicate facets in the extracted data. If we just print some random coloring, that doesn't matter:

lens = Length /@ AllFacets;
cols = Table[Blend[{Hue[RandomReal[{0, 1}]], Pink}], {#}] & /@ lens;

Show[Graphics3D[Transpose[{cols[[3]], AllFacets[[3]]}]], 
 Graphics3D[Arrow[{{0, 0, 0}, 6 #}] & /@ IdentityMatrix[3]],
 ViewVertical -> {0, 1, 0}, ViewPoint -> {2, 0, 2}, Boxed -> False,
 PlotRange -> {{-6, 6}, {-1, 12}, {-6, 6}}, ImageSize -> 700] 

pink rainbow knight

Okay this looks too much like a discothèque, so for the sake of simplicity, we next need to refine until the facets of each voxel share the same color. More work to be done in the next few days or weeks, but we seem to be making progress.

POSTED BY: Brad Klee
8 Replies

@Frederick Wu: Nice find and reinterpretation of Bauhaus abstract chess set, and here's another reference). Good idea to save packing space making certain pieces complementary to one another. However, why color the pieces uniformly?

Recall that the Bauhaus program ultimately failed at defending Germany against occultism, pseudoscience, and other forms of insanity, perhaps because of extremist divisions between "teams"? Once holier-than-thou whites have gone off to an arms race with genocidal blacks (or vice-versa) obviously we are talking about much worse than a "friendly game"--new weapons of mass destruction, anyone? No thanks, not for me!

My design goal was to find the smallest representative (as opposed to abstract) set, using two colors with accents. No piece is totally orange or totally yellow... that is important. I think it would be difficult to make knights or bishops any smaller, so "goal met".

@Kapio: Yes, deployment to metaverse or blockchain is easiest when storage size of assets is kept to a minimum. Team chess on Blockchain is an interesting thought experiment right now. A set of peers is divided in two teams, and at each update teams vote on candidate moves. The chain sequentially stores game states, which are decided on by distributed consensus, until checkmate is reached. Then a new game starts or the chain goes idle. The question is how much should be stored on chain? All candidate moves & user votes? Maximalist storage could lead to interesting analysis post-game, i.e. what if we had listened to so-and-so's unique move instead of deciding democratically?

The other question is about controls. We now see a lot of really great indie content on platforms like Steam or Nintendo Switch. Will we ever be able to control a Mathematica graphics environment using some sort of device or gadget like the Nintendo switch? Voxel Chess is an obvious case for prototyping (starting w/ finitely many isomorphic viewpoints).

For now, we can already animate an entire game. What follows is a relatively low frill replay of Turochamp vs. Glennie (1952). The most difficult part is actually converting from horrible shorthand notation to FEN, but I already did this part almost 10 years ago. Not much code later...

GameBoard = Graphics3D[{EdgeForm[None],
    ImportVox["~/OpenAssets/WorldChess/TXT/OYChessboardVox.txt"] /. {
      Cuboid[{x_, y_, z_}] :> Cuboid[{x - 33, y - 33, z}]}}, 
   Boxed -> False, ImageSize -> 1000,
   ViewVertical -> {0, 0, 1}, ViewPoint -> {-100, 100, 100}];

ObjNames = {"Pawn", "Rook", "Knight", "Bishop", "Queen", "King"};

YPieces =   Graphics3D[{EdgeForm[None], 
      ImportVox["~/OpenAssets/WorldChess/TXT/Y" <> # <> "Vox.txt"]}, 
     Boxed -> False, ImageSize -> 100,
     ViewVertical -> {0, 0, 1}, ViewPoint -> {-100, 100, 75}] & /@ ObjNames;

OPieces = Graphics3D[{EdgeForm[None], 
      ImportVox["~/OpenAssets/WorldChess/TXT/O" <> # <> "Vox.txt"]}, 
     Boxed -> False, ImageSize -> 100,
     ViewVertical -> {0, 0, 1}, ViewPoint -> {100, 100, 75}] & /@ ObjNames;

ImportGame[file_] := With[{dat = 
    DeleteCases[StringSplit[#, " "], ""][[3]] & /@ 
     StringSplit[Import[file], "\n"][[5 ;; -1]]}, dat]

ZeroRep = ToString[#] -> StringJoin[Table["0", {#}]] & /@ Range[8]

 ToBoard[fen_] :=  Map[Characters, StringReplace[#,
     ZeroRep] & /@ StringSplit[fen, "/"]]

StringSyms = {"P", "R", "N", "B", "Q", "K", "p", "r", "n", "b", "q", "k"};
AllPieces = Join[YPieces, OPieces];
ref = Join[Table[1, {6}], Table[-1, {6}]];
off = Join[Table[0, {6}], Table[-1, {6}]];

CopyPlace[piece_, locs_, ref_, off_] :=  ReplaceAll[piece, 
    Cuboid[{x_, y_, z_}] :>  Cuboid[{y, ref x + off, z} + {8 (#[[2]] - 4) - 4, 
        8 (-#[[1]] + 4) + 4, 2}]] & /@ locs

GameState[board_] :=  Show[GameBoard,  MapThread[
   CopyPlace, {AllPieces, Position[board, #] & /@ StringSyms, ref, 
    off}]]

GameList = {
   "~/OpenAssets/WorldChess/GAME/BogartBacall1951.txt", 
   "~/OpenAssets/WorldChess/GAME/CharlesEvans2002.txt", 
   "~/OpenAssets/WorldChess/GAME/ByrneFischer1956.txt", 
   "~/OpenAssets/WorldChess/GAME/EinsteinOppenheimer1933.txt", 
   "~/OpenAssets/WorldChess/GAME/TuringGlennie1952.txt", 
   "~/OpenAssets/WorldChess/GAME/GlaurungGlaurung2012.txt"};

BoardData = ToBoard /@ ImportGame[GameList[[5]] ];

AbsoluteTiming[glist = GameState[#] & /@ BoardData;]
ListAnimate[glist]

turochamp game

Result: The Human Wins!

POSTED BY: Brad Klee

Super-awesome! Is this viable for Second Life, Minecraft, Unity, Unreal, Meta, or other 3D worlds environments coming at us rapidly?

POSTED BY: Kapio Letto

enter image description here -- you have earned Featured Contributor Badge enter image description here Your exceptional post has been selected for our editorial column Staff Picks http://wolfr.am/StaffPicks and Your Profile is now distinguished by a Featured Contributor Badge and is displayed on the Featured Contributor Board. Thank you!

POSTED BY: EDITORIAL BOARD
Posted 3 years ago

Dear Brad,

It's very interesting and beautiful design, Thank you for share.

I was inspired by Bauhaus Chess Set, so I also designed a Minimalism version. I upload a few image for you to know the concept, the packing size will also be minimal. Later I build it by Lego with my daughter and played for a while. We have fun.

enter image description here enter image description here enter image description here enter image description here enter image description here

POSTED BY: Frederick Wu

For a true vox file we also need to list colors. This becomes more important when the particular asset has more than two colors. Here is revised export function and an import function to pair with it:

VoxFileFormat[verts_, objname_, SlabSize_, DefaultCols_] := Join[{
   StringJoin["Vox file for ", objname],
   StringJoin[SlabSize, " slab size "],
   "---------------------",
   " x    y    z    c    ",
   "---------------------"},
  Flatten[MapIndexed[Function[{vert},
        Append[vert, #2[[1]] ]] /@ #1 &, verts], 
    1] /. {x_, y_, z_, c_} :> StringJoin[
     If[Sign[x] == 1, " ", "-"],
     StringPadRight[ToString[Abs[x]], 4, " "],
     If[Sign[y] == 1, " ", "-"],
     StringPadRight[ToString[Abs[y]], 4, " "],
     If[Sign[z] == 1, " ", "-"],
     StringPadRight[ToString[Abs[z]], 4, " "],
     If[Sign[c] == 1, " ", "-"],
     StringPadRight[ToString[c], 2, " "]
     ], {
   "---------------------",
   " c    R    G    B    ",
   "---------------------"},
  MapIndexed[StringJoin[
     " ", StringPadRight[ToString[#2[[1]]], 4, " "],
     " ", StringPadRight[ToString[#1[[1]]], 4, " "],
     " ", StringPadRight[ToString[#1[[2]]], 4, " "],
     " ", StringPadRight[ToString[#1[[3]]], 4, " "]
     ] &, DefaultCols]
  ]

ToCubes[verts_, cols_] :=  With[
  {colRep = Rule[#[[1]], RGBColor[#[[2 ;; 4]]/255]] & /@ cols},
  {#[[4]] /. colRep, Cuboid[#[[1 ;; 3]]]} & /@ verts]

ImportVox[file_] := With[{lines = StringSplit[Import[file], "\n"]},
  Function[{markers},    ToCubes[ Map[ToExpression, DeleteCases[
       StringSplit[lines[[ markers[[2]] + 1 ;; markers[[3]] - 1  ]], 
        " "], "", Infinity], {2}], Map[ToExpression, 
      DeleteCases[ StringSplit[lines[[ markers[[4]] + 1 ;; -1  ]], " "], "", 
       Infinity], {2}]] ][Flatten[Position[lines, "---------------------"]]]]

I then used the export function to create new files and push to github. Then we can import:

GameBoard =  Graphics3D[{EdgeForm[None], 
   ImportVox["~/OpenAssets/WorldChess/TXT/OYChessboardVox.txt"]}, 
  Boxed -> False, ImageSize -> 1000,
  ViewVertical -> {0, 0, 1}, ViewPoint -> {-100, 100, 75}]

chess board

YPieces =  Graphics3D[{EdgeForm[None], 
     ImportVox["~/OpenAssets/WorldChess/TXT/Y" <> # <> "Vox.txt"]}, 
    Boxed -> False, ImageSize -> 100,
    ViewVertical -> {0, 0, 1}, ViewPoint -> {-100, 100, 75}] & /@ ObjNames

Y Pieces

OPieces =  Graphics3D[{EdgeForm[None], 
     ImportVox["~/OpenAssets/WorldChess/TXT/O" <> # <> "Vox.txt"]}, 
    Boxed -> False, ImageSize -> 100, ViewVertical -> {0, 0, 1}, 
   ViewPoint -> {100, 100, 75}] & /@ ObjNames

O Pieces

Show[ GameBoard,
 YPieces[[1]] /.   Cuboid[{x_, y_, z_}] :> 
   Cuboid[{x + 5 + 8 #, y + 13, z + 2}] & /@ Range[0, 7],
 YPieces[[2]] /.   Cuboid[{x_, y_, z_}] :> 
     Cuboid[{x + 5 + 8 #, y + 5, z + 2}] & /@ {0, 7},
 YPieces[[3]] /. Cuboid[{x_, y_, z_}] :> 
     Cuboid[{y + 5 + 8 #, x + 5, z + 2}] & /@ {1, 6},
 YPieces[[4]] /. Cuboid[{x_, y_, z_}] :> 
     Cuboid[{y + 5 + 8 #, x + 5, z + 2}] & /@ {2, 5},
 YPieces[[5]] /. Cuboid[{x_, y_, z_}] :> 
     Cuboid[{y + 5 + 8 #, x + 5, z + 2}] & /@ {3},
 YPieces[[6]] /. Cuboid[{x_, y_, z_}] :> 
     Cuboid[{y + 5 + 8 #, x + 5, z + 2}] & /@ {4},

 OPieces[[1]] /. Cuboid[{x_, y_, z_}] :> 
     Cuboid[{x + 5 + 8 #, y + 13 + 8 5, z + 2}] & /@ Range[0, 7],
 OPieces[[2]] /. Cuboid[{x_, y_, z_}] :> 
     Cuboid[{x + 5 + 8 #, -y + 4 + 8 7, z + 2}] & /@ {0, 7},
 OPieces[[3]] /. Cuboid[{x_, y_, z_}] :> 
     Cuboid[{y + 5 + 8 #, -x + 4 + 8 7, z + 2}] & /@ {1, 6},
 OPieces[[4]] /. Cuboid[{x_, y_, z_}] :> 
     Cuboid[{y + 5 + 8 #, -x + 4 + 8 7, z + 2}] & /@ {2, 5},
 OPieces[[5]] /. Cuboid[{x_, y_, z_}] :> 
     Cuboid[{y + 5 + 8 #, -x + 4 + 8 7, z + 2}] & /@ {3},
 OPieces[[6]] /. Cuboid[{x_, y_, z_}] :> 
     Cuboid[{y + 5 + 8 #, -x + 4 + 8 7, z + 2}] & /@ {4},
 ViewVertical -> {0, 0, 1}, ViewPoint -> {-100, 100, 75}]

Chess Game

Now if we had a rival player, we could turn this BB into a metaverse by posting and responding, move by move until checkmate is reached, but we might need a few more functions. And while we're at it, here's an article about chess on block chain.

POSTED BY: Brad Klee

Thank you for this presentation, Brad! Very well demonstrated.

The coloring is relatively easy to do, but it is tedious and takes time. In this case, we don't want extremist pieces, either all black or all white. We will take subsets out of each piece and color them differently.

BaseCol = {{_, 0 | 1, _} -> 0, {-1 | 0, 2, -2 | 1} -> 1, {-2 | 1, 2, -1 | 0} -> 1};

ColReps = {{}, {{_, 9 | 10, -3 | 2} -> 1, {-3 | 2, 9 | 10, _} -> 1},
   {{0, 9, -1 | 0} -> 1, {-2, 8 | 9 | 10, -1 | 0} ->  1 , 
    {-3, 9 | 8 | 7 | 6 | 5 | 4, -1 | 0} -> 1  },
   {{-1 | 0, 10 | 11 | 12, -1} -> 1   }, {{-1 | 0, 14 | 15, -1 | 0} -> 1  },
   {{-1 | 0, 11, -2 | 1} -> 1, {-2 | 1, 11, -1 | 0} -> 1}};

DefaultCol = {{_, _, _} -> 0};

g1 = Function[{col1, col2}, Show[MapThread[Graphics3D[
          Transpose[{#1 /. BaseCol /. #3 /. DefaultCol /. {0 -> col1, 
              1 -> col2},
            #1 /. {x_, y_, z_} :> Cuboid[{x , y, z + #2*8}]}]
          ] &, {AllVoxels, Range[6], ColReps}], ImageSize -> 700, 
       ViewVertical -> {0, 1, 0},
       ViewPoint -> {4, 1, -2}, Boxed -> False]] @@ # & /@ {{Yellow, 
     Orange}, {Orange, Yellow}};

Column[Show[#, ImageSize -> 500] & /@ g1]

Colored Chess Pieces

With a few lines of extra formatting, we can export these to a vox plaintext file

SixCols = MapThread[#1 /. BaseCol /. #3 /. DefaultCol &, 
    {AllVoxels, Range[6], ColReps}];
SixVerts = AllVoxels;
ObjNames = {"Pawn", "Rook", "Knight", "Bishop", "Queen", "King"};

VoxFileFormat[verts_, cols_, objname_] := With[{header = {
     StringJoin["Vox file for ", objname],
     "6x16x6 slab size ",
     "----------------",
     " x    y    z   c",
     "----------------"
     }},
  Join[header,
   MapThread[Append, {verts, cols}] /. {x_, y_, z_, c_} :> 
     StringJoin[
      If[Sign[x] == 1, " ", "-"],
      StringPadRight[ToString[Abs[x]], 4, " "],
      If[Sign[y] == 1, " ", "-"],
      StringPadRight[ToString[Abs[y]], 4, " "],
      If[Sign[z] == 1, " ", "-"],
      StringPadRight[ToString[Abs[z]], 4, " "],
      StringPadRight[ToString[c], 2, " "]
      ]]]

ObjFiles = MapThread[VoxFileFormat, {SixVerts, SixCols, ObjNames}];

MapThread[
  Export["~/OpenAssets/WorldChess/TXT/" <> #1 <> 
     "Vox.txt", #2] &, {ObjNames, ObjFiles}];

ChessPiecesData = 
  Map[ToExpression[DeleteCases[StringSplit[#, " "], ""]] &, 
     StringSplit[#, "\n"][[6 ;; -1]]] & /@ 
   Map[Import["~/OpenAssets/WorldChess/TXT/" <> #1 <> "Vox.txt"] &, 
    ObjNames];

Show[MapIndexed[
  Graphics3D[#1 /. {x_, y_, z_, 
       c_} :> {c /. {1 -> Yellow, 0 -> Orange},
       Cuboid[{x , y, z + #2[[1]] 8}]}] &, ChessPiecesData],
 ImageSize -> 700, ViewVertical -> {0, 1, 0}, ViewPoint -> {4, 1, -2},
  Boxed -> False]

Orange Pieces

The plaintext files are now up on github, but we still need a board. Next task...

POSTED BY: Brad Klee

enter image description here

The two advantages to using half distances are: 1. If smallest lattice distance is the same as edge length, then it is not easy to find a correct set of edges. 2. Voxel centers fall on sublattice between Voxel corners. So we can think about projecting to voxels as carving out a slab of stone:

Sculpting

How do we know to chisel away the point in red? Check separately on three dimensions to find where the point falls into the stack of facets

VoxelMemberQ[FacetList_, LatticePt_] := And @@ MapThread[
   EvenQ[Position[Sort[Append[#1, #2]], #2][[1, 1]]] &, {
    Function[{ind}, Union[Flatten[Select[FacetList[[All, 1]], 
          MemberQ[#, ReplacePart[LatticePt, ind -> _]] &][[All, All, 
          ind]]]]] /@ Range[3], LatticePt}]

Facets2Voxels[facets_] :=  With[{Slab = Flatten[Table[{i + 1/2, j + 1/2, k + 1/2},
      {i, -3, 2}, {j, 0, 16}, {k, -3, 2}], 2]},   Map[# - {1/2, 1/2, 1/2} &,
   Select[Slab, VoxelMemberQ[facets, # ] &]]]

AbsoluteTiming[AllVoxels = Facets2Voxels /@ AllFacets; ]

Show[MapIndexed[ Graphics3D[#1 /. {x_, y_, z_} :> Cuboid[{x , y, z + #2[[1]] 8}]
] &,  AllVoxels], ImageSize -> 700, ViewVertical -> {0, 1, 0}, 
ViewPoint -> {4, 1, -2},  Boxed -> False]
(*Slow?*)
Out[]={158,Null}

voxel pieces

And here's a cheap extra function for Pawn promotion:

SurfaceVoxels[voxels_] := Select[voxels, Length[
  Nearest[Complement[voxels, {#}], #]] != 6 &]

IterateTransorm[pFrom_, pTo_] :=  Union[Flatten[Nearest[Complement[pTo, {#}],
      #] & /@  Complement[pFrom, Complement[SurfaceVoxels[pFrom ], pTo]], 1]]

Pawn2Knight = NestList[IterateTransorm[#, AllVoxels[[3]] ] &, AllVoxels[[1]], 10];

Pawn2Queen = NestList[IterateTransorm[#, AllVoxels[[5]] ] &, AllVoxels[[1]], 10];

Pawn 2 Knight

Pawn 2 Queen

Most people these days want to see pawn to queen, but there are certain situations where pawn to knight is advantageous. Especially if the opponent king is well guarded, a jumper could be useful. Of course we can also color the voxels,

cols = Table[Blend[{Hue[RandomReal[{0, 1}]], Pink}], {Length@AllVoxels[[3]]}];

Graphics3D[Transpose[{cols, Cuboid /@ AllVoxels[[3]]}],
 ViewVertical -> {0, 1, 0}, ViewPoint -> {4, 2, -2},
 PlotRange -> {{-6, 6}, {-1, 16}, {-6, 6}}, Boxed -> False, 
 ImageSize -> 700]

disco knight

This coloring, while an improvement over the previous attempt, unfortunately, still looks too much like a Discothèque. I guess this would be fine for "Ready Player 1", but it is also irrespective of Western History where pieces are either Black or White. So we probably need to work a little more (later) to find a compromise how to color the pieces more appropriately.

Edit Nov. 26 An alternate, prob. better, animation for pawn to queen promotion follows:

Pawn to Queen

Suspicious that the animation repeats only twice. Thought that the code said $42$, but it's gone missing instead. Accidentally deleted?

POSTED BY: Brad Klee

Group Abstract Group Abstract