PageRenderTime 29ms CodeModel.GetById 0ms RepoModel.GetById 1ms app.codeStats 0ms

/src/quag.geek.nz/mcobj/mcobj.go

http://github.com/quag/mcobj
Go | 527 lines | 482 code | 43 blank | 2 comment | 90 complexity | 90efdff29ad719693346b1b292836233 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. package main
  2. import (
  3. "bufio"
  4. "flag"
  5. "fmt"
  6. "io/ioutil"
  7. "json"
  8. "math"
  9. "os"
  10. "path/filepath"
  11. "quag.geek.nz/nbt"
  12. "runtime"
  13. "strconv"
  14. "strings"
  15. "quag.geek.nz/mcobj/commandline"
  16. )
  17. var (
  18. out *bufio.Writer
  19. yMin int
  20. blockFaces bool
  21. hideBottom bool
  22. noColor bool
  23. faceCount int
  24. faceLimit int
  25. chunkCount int
  26. obj3dsmax bool
  27. )
  28. func main() {
  29. var bx, bz float64
  30. var cx, cz int
  31. var square int
  32. var rectx, rectz int
  33. var maxProcs = runtime.GOMAXPROCS(0)
  34. var prt bool
  35. var solidSides bool
  36. var mtlNumber bool
  37. var defaultObjOutFilename = "a.obj"
  38. var defaultPrtOutFilename = "a.prt"
  39. commandLine := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
  40. var outFilename string
  41. commandLine.IntVar(&maxProcs, "cpu", maxProcs, "Number of cores to use")
  42. commandLine.StringVar(&outFilename, "o", defaultObjOutFilename, "Name for output file")
  43. commandLine.IntVar(&yMin, "y", 0, "Omit all blocks below this height. 63 is sea level")
  44. commandLine.BoolVar(&solidSides, "sides", false, "Solid sides, rather than showing underground")
  45. commandLine.BoolVar(&blockFaces, "bf", false, "Don't combine adjacent faces of the same block within a column")
  46. commandLine.BoolVar(&hideBottom, "hb", false, "Hide bottom of world")
  47. commandLine.BoolVar(&noColor, "g", false, "Omit materials")
  48. commandLine.Float64Var(&bx, "x", 0, "Center x coordinate in blocks")
  49. commandLine.Float64Var(&bz, "z", 0, "Center z coordinate in blocks")
  50. commandLine.IntVar(&cx, "cx", 0, "Center x coordinate in chunks")
  51. commandLine.IntVar(&cz, "cz", 0, "Center z coordinate in chunks")
  52. commandLine.IntVar(&square, "s", math.MaxInt32, "Chunk square size")
  53. commandLine.IntVar(&rectx, "rx", math.MaxInt32, "Width(x) of rectangle size")
  54. commandLine.IntVar(&rectz, "rz", math.MaxInt32, "Height(z) of rectangle size")
  55. commandLine.IntVar(&faceLimit, "fk", math.MaxInt32, "Face limit (thousands of faces)")
  56. commandLine.BoolVar(&prt, "prt", false, "Write out PRT file instead of Obj file")
  57. commandLine.BoolVar(&obj3dsmax, "3dsmax", false, "Create .obj file compatible with 3dsMax")
  58. commandLine.BoolVar(&mtlNumber, "mtlnum", false, "Number materials instead of using names")
  59. var showHelp = commandLine.Bool("h", false, "Show Help")
  60. commandLine.Parse(os.Args[1:])
  61. runtime.GOMAXPROCS(maxProcs)
  62. fmt.Printf("mcobj %v (cpu: %d) Copyright (c) 2011 Jonathan Wright\n", version, runtime.GOMAXPROCS(0))
  63. exeDir, _ := filepath.Split(strings.Replace(os.Args[0], "\\", "/", -1))
  64. if *showHelp || commandLine.NArg() == 0 {
  65. settingsPath := filepath.Join(exeDir, "settings.txt")
  66. fi, err := os.Stat(settingsPath)
  67. if err == nil && (fi.IsRegular() || fi.IsSymlink()) {
  68. line, err := ioutil.ReadFile(settingsPath)
  69. if err != nil {
  70. fmt.Fprintln(os.Stderr, "ioutil.ReadFile:", err)
  71. } else {
  72. parseFakeCommandLine(commandLine, line)
  73. }
  74. }
  75. if commandLine.NArg() == 0 {
  76. fmt.Fprintln(os.Stderr)
  77. fmt.Fprintln(os.Stderr, "Usage: mcobj -cpu 4 -s 20 -o world1.obj", ExampleWorldPath)
  78. fmt.Fprintln(os.Stderr)
  79. commandLine.PrintDefaults()
  80. fmt.Println()
  81. stdin := bufio.NewReader(os.Stdin)
  82. for commandLine.NArg() == 0 {
  83. fmt.Printf("command line: ")
  84. line, _, err := stdin.ReadLine()
  85. if err == os.EOF {
  86. fmt.Println()
  87. return
  88. } else if err != nil {
  89. fmt.Fprintln(os.Stderr, "stdin.ReadLine:", err)
  90. return
  91. }
  92. parseFakeCommandLine(commandLine, line)
  93. fmt.Println()
  94. }
  95. }
  96. }
  97. manualCenter := false
  98. commandLine.Visit(func(f *flag.Flag) {
  99. switch f.Name {
  100. case "x":
  101. fallthrough
  102. case "z":
  103. fallthrough
  104. case "cx":
  105. fallthrough
  106. case "cz":
  107. manualCenter = true
  108. }
  109. })
  110. if faceLimit != math.MaxInt32 {
  111. faceLimit *= 1000
  112. }
  113. if mtlNumber {
  114. MaterialNamer = new(NumberBlockIdNamer)
  115. } else {
  116. MaterialNamer = new(NameBlockIdNamer)
  117. }
  118. switch {
  119. case bx == 0 && cx == 0:
  120. cx = 0
  121. case cx == 0:
  122. cx = int(math.Floor(bx / 16))
  123. }
  124. switch {
  125. case bz == 0 && cz == 0:
  126. cz = 0
  127. case cz == 0:
  128. cz = int(math.Floor(bz / 16))
  129. }
  130. if prt && outFilename == defaultObjOutFilename {
  131. outFilename = defaultPrtOutFilename
  132. }
  133. if solidSides {
  134. defaultSide = &emptySide
  135. }
  136. {
  137. var jsonError = loadBlockTypesJson(filepath.Join(exeDir, "blocks.json"))
  138. if jsonError != nil {
  139. fmt.Fprintln(os.Stderr, "blocks.json error:", jsonError)
  140. return
  141. }
  142. }
  143. settings := &ProcessingSettings{
  144. Prt: prt,
  145. OutFilename: outFilename,
  146. MaxProcs: maxProcs,
  147. ManualCenter: manualCenter,
  148. Cx: cx,
  149. Cz: cz,
  150. Square: square,
  151. Rectx: rectx,
  152. Rectz: rectz,
  153. }
  154. validPath := false
  155. for _, dirpath := range commandLine.Args() {
  156. var fi, err = os.Stat(dirpath)
  157. validPath = validPath || (err == nil && !fi.IsDirectory())
  158. }
  159. if validPath {
  160. for _, dirpath := range commandLine.Args() {
  161. processWorldDir(dirpath, settings)
  162. }
  163. } else {
  164. processWorldDir(strings.Join(commandLine.Args(), " "), settings)
  165. }
  166. }
  167. func parseFakeCommandLine(commandLine *flag.FlagSet, line []byte) {
  168. args := commandline.SplitCommandLine(strings.Trim(string(line), " \r\n"))
  169. if len(args) >= 1 && args[0] == "mcobj" {
  170. args = args[1:]
  171. }
  172. for i, arg := range args {
  173. if strings.HasPrefix(arg, "~/") {
  174. args[i] = filepath.Join(os.Getenv("HOME"), arg[2:])
  175. } else if strings.HasPrefix(strings.ToUpper(arg), "%APPDATA%\\") || strings.HasPrefix(strings.ToUpper(arg), "%APPDATA%/") {
  176. args[i] = filepath.Join(os.Getenv("APPDATA"), arg[len("%APPDATA%/"):])
  177. }
  178. }
  179. commandLine.Parse(args)
  180. }
  181. type ProcessingSettings struct {
  182. Prt bool
  183. OutFilename string
  184. MaxProcs int
  185. ManualCenter bool
  186. Cx, Cz int
  187. Square int
  188. Rectx, Rectz int
  189. }
  190. func processWorldDir(dirpath string, settings *ProcessingSettings) {
  191. var fi, err = os.Stat(dirpath)
  192. if err != nil {
  193. fmt.Fprintln(os.Stderr, "World error:", err)
  194. return
  195. } else if !fi.IsDirectory() {
  196. fmt.Fprintln(os.Stderr, dirpath, "is not a directory")
  197. }
  198. // Pick cx, cz
  199. var cx, cz int
  200. if settings.ManualCenter {
  201. cx, cz = settings.Cx, settings.Cz
  202. } else {
  203. var file, fileErr = os.Open(filepath.Join(dirpath, "level.dat"))
  204. defer file.Close()
  205. if fileErr != nil {
  206. fmt.Fprintln(os.Stderr, "os.Open", fileErr)
  207. return
  208. }
  209. level, err := nbt.ReadLevelDat(file)
  210. if err != nil {
  211. fmt.Fprintln(os.Stderr, "nbt.ReadLevelDat", err)
  212. return
  213. }
  214. file.Close()
  215. cx, cz = level.SpawnX / 16, level.SpawnZ / 16
  216. }
  217. // Create ChunkMask
  218. var (
  219. chunkMask ChunkMask
  220. chunkLimit int
  221. )
  222. if settings.Square != math.MaxInt32 {
  223. chunkLimit = settings.Square * settings.Square
  224. var h = settings.Square / 2
  225. chunkMask = &RectangeChunkMask{cx - h, cz - h, cx - h + settings.Square, cz - h + settings.Square}
  226. } else if settings.Rectx != math.MaxInt32 || settings.Rectz != math.MaxInt32 {
  227. switch {
  228. case settings.Rectx != math.MaxInt32 && settings.Rectz != math.MaxInt32:
  229. chunkLimit = settings.Rectx * settings.Rectz
  230. var (
  231. hx = settings.Rectx / 2
  232. hz = settings.Rectz / 2
  233. )
  234. chunkMask = &RectangeChunkMask{cx - hx, cz - hz, cx - hx + settings.Rectx, cz - hz + settings.Rectz}
  235. case settings.Rectx != math.MaxInt32:
  236. chunkLimit = math.MaxInt32
  237. var hx = settings.Rectx / 2
  238. chunkMask = &RectangeChunkMask{cx - hx, math.MinInt32, cx - hx + settings.Rectx, math.MaxInt32}
  239. case settings.Rectz != math.MaxInt32:
  240. chunkLimit = math.MaxInt32
  241. var hz = settings.Rectz / 2
  242. chunkMask = &RectangeChunkMask{math.MinInt32, cz - hz, math.MaxInt32, cz - hz + settings.Rectz}
  243. }
  244. } else {
  245. chunkLimit = math.MaxInt32
  246. chunkMask = &AllChunksMask{}
  247. }
  248. var world = OpenWorld(dirpath, chunkMask)
  249. var pool, poolErr = world.ChunkPool()
  250. if poolErr != nil {
  251. fmt.Fprintln(os.Stderr, "Chunk pool error:", poolErr)
  252. return
  253. }
  254. var generator OutputGenerator
  255. if settings.Prt {
  256. generator = new(PrtGenerator)
  257. } else {
  258. generator = new(ObjGenerator)
  259. }
  260. var boundary = new(BoundaryLocator)
  261. boundary.Init()
  262. var startErr = generator.Start(settings.OutFilename, pool.Remaining(), settings.MaxProcs, boundary)
  263. if startErr != nil {
  264. fmt.Fprintln(os.Stderr, "Generator start error:", startErr)
  265. return
  266. }
  267. if walkEnclosedChunks(pool, world, chunkMask, chunkLimit, cx, cz, generator.GetEnclosedJobsChan()) {
  268. <-generator.GetCompleteChan()
  269. }
  270. var closeErr = generator.Close()
  271. if closeErr != nil {
  272. fmt.Fprintln(os.Stderr, "Generator close error:", closeErr)
  273. return
  274. }
  275. }
  276. type OutputGenerator interface {
  277. Start(outFilename string, total int, maxProcs int, boundary *BoundaryLocator) os.Error
  278. GetEnclosedJobsChan() chan *EnclosedChunkJob
  279. GetCompleteChan() chan bool
  280. Close() os.Error
  281. }
  282. type EnclosedChunkJob struct {
  283. last bool
  284. enclosed *EnclosedChunk
  285. }
  286. func walkEnclosedChunks(pool ChunkPool, opener ChunkOpener, chunkMask ChunkMask, chunkLimit int, cx, cz int, enclosedsChan chan *EnclosedChunkJob) bool {
  287. var (
  288. sideCache = new(SideCache)
  289. started = false
  290. )
  291. for i := 0; moreChunks(pool.Remaining(), chunkLimit); i++ {
  292. for x := 0; x < i && moreChunks(pool.Remaining(), chunkLimit); x++ {
  293. for z := 0; z < i && moreChunks(pool.Remaining(), chunkLimit); z++ {
  294. var (
  295. ax = cx + unzigzag(x)
  296. az = cz + unzigzag(z)
  297. )
  298. if pool.Pop(ax, az) {
  299. loadSide(sideCache, opener, chunkMask, ax-1, az)
  300. loadSide(sideCache, opener, chunkMask, ax+1, az)
  301. loadSide(sideCache, opener, chunkMask, ax, az-1)
  302. loadSide(sideCache, opener, chunkMask, ax, az+1)
  303. var chunk, loadErr = loadChunk2(opener, ax, az)
  304. if loadErr != nil {
  305. fmt.Println(loadErr)
  306. } else {
  307. var enclosed = sideCache.EncloseChunk(chunk)
  308. sideCache.AddChunk(chunk)
  309. chunkCount++
  310. enclosedsChan <- &EnclosedChunkJob{!moreChunks(pool.Remaining(), chunkLimit), enclosed}
  311. started = true
  312. }
  313. }
  314. }
  315. }
  316. }
  317. return started
  318. }
  319. type Blocks []uint16
  320. type BlockColumn []uint16
  321. func (b *Blocks) Get(x, y, z int) uint16 {
  322. return (*b)[y+(z*128+(x*128*16))]
  323. }
  324. func (b *Blocks) Column(x, z int) BlockColumn {
  325. var i = 128 * (z + x*16)
  326. return BlockColumn((*b)[i : i+128])
  327. }
  328. func zigzag(n int) int {
  329. return (n << 1) ^ (n >> 31)
  330. }
  331. func unzigzag(n int) int {
  332. return (n >> 1) ^ (-(n & 1))
  333. }
  334. func moreChunks(unprocessedCount, chunkLimit int) bool {
  335. return unprocessedCount > 0 && faceCount < faceLimit && chunkCount < chunkLimit
  336. }
  337. func loadChunk(filename string) (*nbt.Chunk, os.Error) {
  338. var file, fileErr = os.Open(filename)
  339. defer file.Close()
  340. if fileErr != nil {
  341. return nil, fileErr
  342. }
  343. var chunk, err = nbt.ReadChunkDat(file)
  344. if err == os.EOF {
  345. err = nil
  346. }
  347. return chunk, err
  348. }
  349. func loadChunk2(opener ChunkOpener, x, z int) (*nbt.Chunk, os.Error) {
  350. var r, openErr = opener.OpenChunk(x, z)
  351. if openErr != nil {
  352. return nil, openErr
  353. }
  354. defer r.Close()
  355. var chunk, nbtErr = nbt.ReadChunkNbt(r)
  356. if nbtErr != nil {
  357. return nil, nbtErr
  358. }
  359. return chunk, nil
  360. }
  361. func loadSide(sideCache *SideCache, opener ChunkOpener, chunkMask ChunkMask, x, z int) {
  362. if !sideCache.HasSide(x, z) && !chunkMask.IsMasked(x, z) {
  363. var chunk, loadErr = loadChunk2(opener, x, z)
  364. if loadErr != nil {
  365. fmt.Println(loadErr)
  366. } else {
  367. sideCache.AddChunk(chunk)
  368. }
  369. }
  370. }
  371. func loadBlockTypesJson(filename string) os.Error {
  372. var jsonBytes, jsonIoError = ioutil.ReadFile(filename)
  373. if jsonIoError != nil {
  374. return jsonIoError
  375. }
  376. var f interface{}
  377. var unmarshalError = json.Unmarshal(jsonBytes, &f)
  378. if unmarshalError != nil {
  379. return unmarshalError
  380. }
  381. var lines, linesOk = f.([]interface{})
  382. if linesOk {
  383. for _, line := range lines {
  384. var fields, fieldsOk = line.(map[string]interface{})
  385. if fieldsOk {
  386. var (
  387. blockId byte
  388. data byte = 255
  389. dataArray []byte
  390. name string
  391. mass SingularOrAggregate = Mass
  392. transparency Transparency = Opaque
  393. empty bool = false
  394. color uint32
  395. )
  396. for k, v := range fields {
  397. switch k {
  398. case "name":
  399. name = v.(string)
  400. case "color":
  401. switch len(v.(string)) {
  402. case 7:
  403. var n, numErr = strconv.Btoui64(v.(string)[1:], 16)
  404. if numErr == nil {
  405. color = uint32(n*0x100 + 0xff)
  406. }
  407. case 9:
  408. var n, numErr = strconv.Btoui64(v.(string)[1:], 16)
  409. if numErr == nil {
  410. color = uint32(n)
  411. }
  412. }
  413. case "blockId":
  414. blockId = byte(v.(float64))
  415. case "data":
  416. switch d := v.(type) {
  417. case float64:
  418. data = byte(d)
  419. case []interface{}:
  420. dataArray = make([]byte, len(d))
  421. for i, value := range d {
  422. dataArray[i] = byte(value.(float64))
  423. }
  424. }
  425. case "item":
  426. if v.(bool) {
  427. mass = Item
  428. transparency = Transparent
  429. } else {
  430. mass = Mass
  431. transparency = Opaque
  432. }
  433. case "transparent":
  434. if v.(bool) {
  435. transparency = Transparent
  436. } else {
  437. transparency = Opaque
  438. }
  439. case "empty":
  440. if v.(bool) {
  441. empty = true
  442. transparency = Transparent
  443. mass = Mass
  444. } else {
  445. empty = false
  446. }
  447. }
  448. }
  449. blockTypeMap[blockId] = &BlockType{blockId, mass, transparency, empty}
  450. if dataArray == nil {
  451. if data != 255 {
  452. extraData[blockId] = true
  453. colors = append(colors, MTL{blockId, data, color, name})
  454. } else {
  455. colors[blockId] = MTL{blockId, data, color, name}
  456. }
  457. } else {
  458. extraData[blockId] = true
  459. for _, data = range dataArray {
  460. colors = append(colors, MTL{blockId, data, color, fmt.Sprintf("%s_%d", name, data)})
  461. }
  462. }
  463. }
  464. }
  465. }
  466. return nil
  467. }