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
Posted 3 years ago

@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

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
Posted 3 years ago
POSTED BY: Brad Klee

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

Posted 3 years ago

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
Posted 3 years ago
POSTED BY: Brad Klee

Group Abstract Group Abstract