js/obj/ObjParser.js
'use strict'
import Bone from '../base/Bone'
import Vector3 from '../base/Vector3'
import Vector4 from '../base/Vector4'
import Material from '../base/Material'
import ObjModel from './ObjModel'
import RenderGroup from '../base/RenderGroup'
import Skin from '../base/Skin'
import TextureUV from '../base/TextureUV'
/**
* ObjParser class
* @access public
*/
export default class ObjParser {
/**
* constructor
* @access public
* @constructor
*/
constructor() {
this._data = null
this._lines = null
this._obj = null
this._parentDirName = null
this._indices = null
//this._materialIndex = 0
this._skinArray = null
//this._normalIndex = 0
this._normalArray = null
this._textureUVArray = null
this._faceNormalArray = null
this._breakFlag = false
this._offset = 0
this._err = 0
this._defaultGroupName = 'default'
this._groupName = 'default'
this._smoothingGroup = 0
this._smoothingVertices = null
this._smoothVerticesHash = null
this._parentDirName = './'
this._normalArray = []
this._textureUVArray = []
this._groupName = this._defaultGroupName
this._smoothVerticesHash = {}
}
setParentDirName(dirName) {
this._parentDirName = dirName
}
setModel(obj) {
this._obj = obj
}
setData(data) {
this._data = data.replace(/( )*\\( )*\n/g, ' ')
this._lines = this._data.split('\n')
}
// FIXME: parse method is unused.
parse(data){
console.log('ObjParser.parse start')
if(data){
this.setData(data)
}
if(!this._obj){
this._obj = new ObjModel()
}
if(!this._obj.skinArray){
this._obj.skinArray = []
this._obj.dynamicSkinOffset = -1
}
this._skinArray = this._obj.skinArray
this._offset = 0
this._err = 0
const result = this.process()
/*
if(this._err){
alert('obj format error:' + this._err)
return null
}
this.splitFaceNormals()
*/
console.log('ObjParser.parse end')
return result
}
process() {
console.log('ObjParser.process start')
let line = ''
this._breakFlag = false
//let v_count = 0
//let f_count = 0
let mtllib_name = ''
while(!this._breakFlag && (line = this.readLine()) !== null){
const tokens = this.getTokens(line)
if(tokens.length === 0){
continue
}
switch(tokens[0]){
case '#': { // comment
continue
}
case 'v': { // geometric vertices
const skin = new Skin()
const pos = new Vector3()
pos.x = parseFloat(tokens[1])
pos.y = parseFloat(tokens[2])
pos.z = parseFloat(tokens[3])
skin.position = pos
skin.boneIndex[0] = 0
skin.boneIndex[1] = -1
skin.boneIndex[2] = -1
skin.boneIndex[3] = -1
skin.skinWeight[0] = 1
skin.skinWeight[1] = 0
skin.skinWeight[2] = 0
skin.skinWeight[3] = 0
skin.normal = null
skin.textureUV = null
skin._smoothingGroup = -1
this._skinArray.push(skin)
//v_count++
break
}
case 'vt': { // texture vertices
const uv = new TextureUV()
uv.u = parseFloat(tokens[1])
uv.v = parseFloat(tokens[2])
this._textureUVArray.push(uv)
break
}
case 'vn': { // vertex normals
const n = new Vector3()
n.x = parseFloat(tokens[1])
n.y = parseFloat(tokens[2])
n.z = parseFloat(tokens[3])
this._normalArray.push(n)
break
}
case 'vp': { // parameter space vertices
break
}
case 'deg': { // degree
break
}
case 'bmat': { // basis matrix
break
}
case 'step': { // step size
break
}
case 'cstype': { // curve or surface type
break
}
case 'p': { // point
break
}
case 'l': { // line
break
}
case 'f': { // face
const num_faces = tokens.length - 4
for(let i=0; i<num_faces; i++){
const data1 = tokens[i+0].split('/')
const data2 = tokens[i+1].split('/')
const data3 = tokens[i+2].split('/')
const nv1 = data1[0]; const nt1 = data1[1]; const nn1 = data1[2]
const nv2 = data2[0]; const nt2 = data2[1]; const nn2 = data2[2]
const nv3 = data3[0]; const nt3 = data3[1]; const nn3 = data2[2]
if(nv1 === '' || nv2 === '' || nv3 === ''){
// error
console.log('format error: f: vertex index')
return null
}
let v1 = this._skinArray[nv1 - 1]
if(v1._smoothingGroup === -1){
// new
v1._smoothingGroup = this._smoothingGroup
if(nt1){
v1.textureUV = this._textureUVArray[nt1 - 1]
}else{
v1.textureUV = null
}
if(nn1){
v1.normal = this._normalArray[nn1 - 1]
}else{
v1.normal = null
}
}else if(v1._smoothingGroup === this._smoothingGroup){
// same group
let checkFace = v1
for(; checkFace; checkFace = checkFace._next){
// check
// check texture
if(nt1){
if(checkFace.texture !== this._textureUVArray[nt1 - 1]){
continue
}
}else{
if(checkFace.texture){
continue
}
}
// check normal
if(nn1){
if(checkFace.normal !== this._normalArray[nn1 - 1]){
continue
}
}else{
if(checkFace.normal){
continue
}
}
break
}
if(checkFace){
v1 = checkFace
}else{
// create skin
}
}else{
// different group
}
const v2 = this._skinArray[nv2 - 1]
const v3 = this._skinArray[nv3 - 1]
}
break
}
case 'curv': { // curve
break
}
case 'curv2': { // 2D curve
break
}
case 'surf': { // surface
break
}
case 'parm': { // parameter values
break
}
case 'trim': { // outer trimming loop
break
}
case 'hole': { // inner trimming loop
break
}
case 'scrv': { // special curve
break
}
case 'sp': { // special point
break
}
case 'end': { // end statement
break
}
case 'con': { // connect
break
}
case 'g': { // group name
if(tokens[1] === ''){
this._groupName = this._defaultGroupName
}else{
this._groupName = tokens[1]
}
break
}
case 's': { // smoothing group
if(tokens[1] === 'off' || tokens[1] <= 0){
this._smoothingGroup = 0
this._smoothingVertices = null
}else{
this._smoothingGroup = tokens[1]
this._smoothingVertices = this._smoothingVerticesHash[this._smoothingGroup]
if(!this._smoothingVertices){
this._smoothingVertices = []
this._smoothingVerticesHash[this._smoothingGroup] = this._smoothingVertices
}
}
break
}
case 'mg': { // merging group
break
}
case 'o': { // object name
break
}
case 'bevel': { // bevel interpolation
break
}
case 'c_interp': { // color interpolation
break
}
case 'd_interp': { // dissolve interpolation
break
}
case 'lod': { // level of detail
break
}
case 'usemtl': { // material name
break
}
case 'mtllib': { // material library
mtllib_name = tokens[1]
console.log('mtllib: ' + mtllib_name)
// FIXME: MTLReader
const baseBone = new Bone()
const renderGroupArray = []
const renderGroupHash = {}
const group = new RenderGroup()
group.boneArray = []
group.boneArray[0] = baseBone
renderGroupArray.push(group)
this._obj.renderGroupArray = renderGroupArray
this._obj.boneArray.push(baseBone)
this._obj.rootBone.addChild(baseBone)
const material = new Material()
material.ambient = new Vector4()
material.diffuse = new Vector4()
material.shininess = 1.0
material.specular = new Vector3()
material.emission = new Vector3()
material.edge = 0
material.texture = null
this._obj.materialArray.push(material)
this._obj.renderGroupArray[0].material = material
break
}
case 'shadow_obj': { // shadow casting
break
}
case 'trace_obj': { // ray tracing
break
}
case 'ctech': { // curve approximation technique
break
}
case 'stech': { // surface approximation technique
break
}
default: { // unknown type
break
}
}
}
if(this._breakFlag){
return false
}
//console.log('v: ' + v_count + '\nf: ' + f_count + '\nmtllib: ' + mtllib_name)
if(this._err){
console.log('obj format error:' + this._err)
return null
}
//this.splitFaceNormals()
console.log('ObjParser.process end')
return true
}
readLine() {
return this._lines.shift()
}
getTokens(line) {
//return line.split(' ').without('')
return line.split(' ').filter((str) => { return str !== '' })
}
/*
moveIndex(len) {
this._text = this._text.substring(len)
this._offset += len
}
*/
/*
skip() {
const str = this._text.match(this._skipPattern)
if(str != null){
const len = str[0].length
this.moveIndex(len)
}
}
*/
/*
getString(pattern) {
this.skip()
const str = this._text.match(pattern)
if(str != null){
this.moveIndex(str[0].length)
return str[0]
}
return null
}
*/
/* matching patterns */
/*
_skipPattern: new RegExp(/^\s+/),
skip() {
const i=0
const code
while(1){
code = this._text.charCodeAt(i)
if(code != 32 && (code < 9 || code > 13)){
break
}
i++
}
if(i>0){
this.moveIndex(i)
}
}
*/
/*
_integerPattern: new RegExp(/^(-|\+)?\d+?/),
getInteger() {
const str = this.getString(this._integerPattern)
const val = parseInt(str)
return val
}
_floatPattern: new RegExp(/^(-|\+)?(\d)*\.(\d)*?/),
getFloat() {
const str = this.getString(this._floatPattern)
const val = parseFloat(str)
return val
}
_commaOrSemicolonPattern: new RegExp(/^,|/),
getCommaOrSemicolon() {
const code = this._text.charCodeAt(0)
if(code == 44 || code == 59){
this.moveIndex(1)
}
}
_wordPattern: new RegExp(/^\w+/),
getWord() {
return this.getString(this._wordPattern)
}
_uuidPattern: new RegExp(/^<[\w-]+>/),
getUUID() {
return this.getString(this._uuidPattern)
}
_leftBracePattern: new RegExp(/^{/),
getLeftBrace() {
return this.getString(this._leftBracePattern)
}
_rightBracePattern: new RegExp(/^}/),
getRightBrace() {
return this.getString(this._rightBracePattern)
}
_memberPattern: new RegExp(/^((array\s+\w+\s+\w+\[(\d+|\w+)\]|\w+\s+\w+)\s*|\[[\w.]+\])/),
getMember() {
return this.getString(this._memberPattern)
}
_filenamePattern: new RegExp(/^'(.*)'?/),
getFilename() {
const str = this.getString(this._filenamePattern)
return RegExp.$1
}
getIntegerArray() {
const n = this.getInteger()
const arr = []
for(var i=0 i<n i++){
arr.push(this.getInteger())
this.getCommaOrSemicolon()
}
return arr
}
getFloatArray() {
const n = this.getInteger()
const arr = []
for(var i=0 i<n i++){
arr.push(this.getInteger())
this.getCommaOrSemicolon()
}
return arr
}
getVector3() {
const v = new Vector3()
v.x = this.getFloat()
v.y = this.getFloat()
v.z = this.getFloat()
this.getCommaOrSemicolon()
return v
}
getVector4() {
const v = new Vector4()
v.x = this.getFloat()
v.y = this.getFloat()
v.z = this.getFloat()
v.w = this.getFloat()
this.getCommaOrSemicolon()
return v
}
// Xファイル読み込み後に頂点をコピー
splitFaceNormals(){
const v = this._faceNormalArray
const vnMap = []
const normals = []
const skins = this._obj.skinArray
const vertexCount = skins.length
// textureCoordsの設定
if(skins[0].textureUV == null){
for(var i=0 i<vertexCount i++){
skins[i].textureUV = new TextureUV()
}
}
// 法線の設定
if(this._faceNormalArray == null){
// 法線が指定されていない場合、自分で計算する。
const ins = this._indices
const numIns = ins.length
const used = []
const n
const n1 = new Vector3()
const n2 = new Vector3()
for(var i=0 i<numIns i++){
if(ins[i].length == 4){
// 四角形
const ii = ins[i]
const s = skins[ii[0]]
n = new Vector3()
if(used[ii[0]]){
s = Object.clone(s)
skins[skins.length] = s
}
used[ii[0]] = true
n1.sub(skins[ii[2]].position, skins[ii[0]].position)
n2.sub(skins[ii[1]].position, skins[ii[0]].position)
n.cross(n1, n2)
n.normalize()
s.normal = n
n = new Vector3()
s = skins[ii[1]]
if(used[ii[1]]){
s = Object.clone(s)
skins[skins.length] = s
}
used[ii[1]] = true
n1.sub(skins[ii[0]].position, skins[ii[1]].position)
n2.sub(skins[ii[2]].position, skins[ii[1]].position)
n.cross(n1, n2)
n.normalize()
s.normal = n
n = new Vector3()
s = skins[ii[2]]
if(used[ii[2]]){
s = Object.clone(s)
skins[skins.length] = s
}
used[ii[2]] = true
n1.sub(skins[ii[1]].position, skins[ii[2]].position)
n2.sub(skins[ii[0]].position, skins[ii[2]].position)
n.cross(n1, n2)
n.normalize()
s.normal = n
n = new Vector3()
s = skins[ii[3]]
if(used[ii[3]]){
s = Object.clone(s)
skins[skins.length] = s
}
if(!(skins[ii[3]] instanceof Object)){
alert('skins[ii[3]] not instance!')
alert('i: ' + i + ', ii[3]: ' + ii[3])
}
used[ii[3]] = true
n1.sub(skins[ii[2]].position, skins[ii[3]].position)
n2.sub(skins[ii[0]].position, skins[ii[3]].position)
n.cross(n1, n2)
n.normalize()
s.normal = n
}else if(ins[i].length == 3){
// 三角形
const ii = ins[i]
const s = skins[ii[0]]
n = new Vector3()
if(used[ii[0]]){
s = Object.clone(s)
skins[skins.length] = s
}
used[ii[0]] = true
n1.sub(skins[ii[2]].position, skins[ii[0]].position)
n2.sub(skins[ii[1]].position, skins[ii[0]].position)
n.cross(n1, n2)
n.normalize()
s.normal = n
n = new Vector3()
s = skins[ii[1]]
if(used[ii[1]]){
s = Object.clone(s)
skins[skins.length] = s
}
used[ii[1]] = true
n1.sub(skins[ii[0]].position, skins[ii[1]].position)
n2.sub(skins[ii[2]].position, skins[ii[1]].position)
n.cross(n1, n2)
n.normalize()
s.normal = n
n = new Vector3()
s = skins[ii[2]]
if(used[ii[2]]){
s = Object.clone(s)
skins[skins.length] = s
}
used[ii[2]] = true
n1.sub(skins[ii[1]].position, skins[ii[2]].position)
n2.sub(skins[ii[0]].position, skins[ii[2]].position)
n.cross(n1, n2)
n.normalize()
s.normal = n
}else{
// 未対応
}
}
}else{
// 同じ頂点に違う法線が指定されている場合、別頂点とする。
for(var i=0 i<vertexCount i++){
vnMap[i] = {}
normals[i] = -1
}
const vSize = v.length
const ins = this._indices
for(var i=0 i<vSize i++){
const vi = v[i]
const ii = ins[i]
for(var j=0 j<vi.length j++){
if(normals[ii[j]] == -1){
// 未登録
normals[ii[j]] = vi[j]
vnMap[ii[j]].set(vi[j], ii[j])
}else if(normals[ii[j]] == vi[j]){
// 登録済み
}else{
const newNo = vnMap[ii[j]].get(vi[j])
if(newNo == null){
// 未登録
newNo = vnMap.length
vnMap[ii[j]].set(vi[j], newNo)
normals[newNo] = vi[j]
//this._obj.skinArray[newNo] = this._obj.skinArray[ii[j]].clone()
this._obj.skinArray[newNo] = Object.clone(this._obj.skinArray[ii[j]])
}else{
// 登録済み
}
}
}
}
for(var i=0 i<normals.length i++){
this._obj.skinArray[i].normal = this._normalArray[normals[i]]
}
}
}
XFileHeader(parent){
const text = this._text
if(!text.match(/^xof (\d\d\d\d)([ \w][ \w][ \w][ \w])(\d\d\d\d)/)){
return false
}
this.moveIndex(16)
this.version = RegExp.$1
this.format = RegExp.$2
this.floatSize = RegExp.$3
return true
}
XObjectLong(parent){
const id = this.getWord()
if(id == null){
return null
}
switch(id){
case 'template':
return this.Template(parent)
case 'Header':
return this.Header(parent)
case 'Mesh':
return this.Mesh(parent)
case 'MeshMaterialList':
return this.MeshMaterialList(parent)
case 'MeshNormals':
return this.MeshNormals(parent)
case 'MeshTextureCoords':
return this.MeshTextureCoords(parent)
case 'MeshVertexColors':
return this.MeshVertexColors(parent)
default:
alert('unknown type:' + id)
break
}
return false
}
ColorRGB(parent) {
const color = new Vector4()
color.x = this.getFloat()
color.y = this.getFloat()
color.z = this.getFloat()
color.w = 1.0
this.getCommaOrSemicolon()
return color
}
ColorRGBA(parent) {
const color = new Vector4()
color.x = this.getFloat()
color.y = this.getFloat()
color.z = this.getFloat()
color.w = this.getFloat()
this.getCommaOrSemicolon()
return color
}
Coords2d(parent) {
const v = new TextureUV()
v.u = this.getFloat()
v.v = this.getFloat()
this.getCommaOrSemicolon()
return v
}
Template(parent) {
const name = this.getWord()
this.getLeftBrace()
const uuid = this.getUUID()
do{
const member = this.getMember()
}while(member != null)
this.getRightBrace()
return true
}
Header(parent) {
this.getLeftBrace()
const major = this.getInteger()
const minor = this.getInteger()
const flags = this.getInteger()
this.getRightBrace()
return true
}
IndexedColor(parent) {
const index = this.getInteger()
const color = this.ColorRGBA()
color.index = index
return color
}
Material(parent) {
this.getLeftBrace()
const material = new Material()
material.ambient = this.ColorRGBA()
material.diffuse = material.ambient
material.shininess = this.getFloat()
material.specular = this.ColorRGB()
material.emission = this.ColorRGB()
material.edge = 0
material.texture = null
const name = this.getWord()
if(name == 'TextureFilename'){
const texture = this.TextureFilename()
if(texture != null){
material.texture = texture
//material.textureFileName = texture.fileName
}
}
this.getRightBrace()
return material
}
Mesh(parent) {
this.getLeftBrace()
// vertices
const nVertices = this.getInteger()
const skinArray = []
for(var i=0 i<nVertices i++){
const skin = new Skin()
const pos = new Vector3()
pos.x = this.getFloat()
pos.y = this.getFloat()
pos.z = -this.getFloat()
skin.position = pos
skin.boneIndex[0] = 0
skin.boneIndex[1] = -1
skin.boneIndex[2] = -1
skin.boneIndex[3] = -1
skin.skinWeight[0] = 1
skin.skinWeight[1] = 0
skin.skinWeight[2] = 0
skin.skinWeight[3] = 0
skinArray.push(skin)
this.getCommaOrSemicolon()
}
this._obj.skinArray = skinArray
this._obj.dynamicSkinOffset = -1
// faces
const nFaces = this.getInteger()
const faces = []
for(var i=0 i<nFaces i++){
const face = this.getIntegerArray()
faces.push(face)
}
this._indices = faces
this.getRightBrace()
return true
}
MeshMaterialList(parent) {
this.getLeftBrace()
// materials
const nMaterials = this.getInteger()
this._materialIndex = 0
const baseBone = new Bone()
const renderGroups = []
for(var i=0 i<nMaterials i++){
const group = new RenderGroup()
group.boneArray = []
group.boneArray[0] = baseBone
renderGroups.push(group)
}
this._obj.renderGroupArray = renderGroups
this._obj.boneArray.push(baseBone)
this._obj.rootBone.addChild(baseBone)
// face materials
const nFaceIndices = this.getInteger()
const indices = this._indices
for(var i=0 i<nFaceIndices i++){
const index = this.getInteger()
this.getCommaOrSemicolon()
const gind = renderGroups[index].indices
const ind = indices[i]
if(ind.length == 3){
gind.push(ind[0])
gind.push(ind[2])
gind.push(ind[1])
}else if(indices[i].length == 4){
gind.push(ind[0])
gind.push(ind[2])
gind.push(ind[1])
gind.push(ind[0])
gind.push(ind[3])
gind.push(ind[2])
}else{
// FIXME: 未対応
}
}
// materials
const material = null
while(1){
const name = this.getWord()
if(name == 'Material'){
material = this.Material(parent)
this._obj.materialArray.push(material)
this._obj.renderGroupArray[this._materialIndex].material = material
this._materialIndex++
}else{
break
}
}
this.getRightBrace()
return true
}
MeshNormals(parent) {
this.getLeftBrace()
const nNormals = this.getInteger()
this._normalArray = []
for(var i=0 i<nNormals i++){
const v = this.getVector3()
v.z = -v.z
this._normalArray.push(v)
}
const nFaceNormals = this.getInteger()
this._faceNormalArray = []
for(var i=0 i<nFaceNormals i++){
const v = this.getIntegerArray()
this._faceNormalArray.push(v)
}
this.getRightBrace()
return true
}
MeshTextureCoords(parent) {
this.getLeftBrace()
const skins = this._obj.skinArray
const nTextureCoords = this.getInteger()
for(var i=0 i<nTextureCoords i++){
skins[i].textureUV = this.Coords2d()
}
this.getRightBrace()
return true
}
MeshVertexColors(parent) {
this.getLeftBrace()
const nVertexColors = this.getInteger()
for(var i=0 i<nVertexColors i++){
const v = this.IndexedColor()
// FIXME: not implemented.
}
this.getRightBrace()
return true
}
TextureFilename(parent) {
this.getLeftBrace()
const name = this.getFilename()
name = name.replace('\\\\', '/')
this.getRightBrace()
const texture = TextureBank.getTexture(this._parentDirName + name)
return texture
}
*/
}