Skip to content

Instantly share code, notes, and snippets.

@koduki
Last active February 12, 2021 10:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save koduki/9abfea857d363d33ffcb698f38d1df5c to your computer and use it in GitHub Desktop.
Save koduki/9abfea857d363d33ffcb698f38d1df5c to your computer and use it in GitHub Desktop.
バーチャルキャストのVCIをRubyで解析
# Read glTF
io = open('suika_vci.vci', "rb")
## Header
glb_h_magic = io.read(4)
glb_h_version = io.read(4).unpack("L*")[0]
glb_h_length = io.read(4).unpack("L*")[0]
### Chunk 0 (JSON)
glb_json_length = io.read(4).unpack("L*")[0]
glb_json_type = io.read(4)
glb_json_data = io.read(glb_json_length)
### Chunk 1 (Buffer)
glb_buff_length = io.read(4).unpack("L*")[0]
glb_buff_type = io.read(4)
glb_buff_data = io.read(glb_buff_length)
# Parse VCI
property = JSON.parse(glb_json_data)
generator = property["asset"]["generator"]
vci_meta = property["extensions"]["VCAST_vci_meta"].slice("title", "version", "authoer")
## Get images
img_idx = property["images"].find{|x| x["name"] == "suikaTex_out"}["bufferView"]
bfv = property["bufferViews"][img_idx]
img_data = glb_buff_data[bfv["byteOffset"], bfv["byteLength"]]
open('suikaTex_out.png', 'wb') do |f|
f.write(img_data.unpack('C*').pack('C*'))
end
img_idx = property["images"].find{|x| x["name"] == "suikaTex_in"}["bufferView"]
bfv = property["bufferViews"][img_idx]
img_data = glb_buff_data[bfv["byteOffset"], bfv["byteLength"]]
open('suikaTex_in.png', 'wb') do |f|
f.write(img_data.unpack('C*').pack('C*'))
end
## Get Lua Scripts
src_idx = property["extensions"]["VCAST_vci_embedded_script"]["scripts"][0]["source"]
bfv = property["bufferViews"][src_idx]
lua_script = glb_buff_data[bfv["byteOffset"], bfv["byteLength"]]
GLB_H_MAGIC = "glTF".b
GLB_H_VERSION = [2].pack("L*")
GLB_JSON_TYPE = "JSON".b
GLB_BUFF_TYPE = "BIN\x00".b
FF = "\x00".b
# Load Template
io = open('WhiteBoard.vci')
glb_h_magic = io.read(4)
glb_h_version = io.read(4).unpack("L*")[0]
glb_h_length = io.read(4).unpack("L*")[0]
glb_json_length = io.read(4).unpack("L*")[0]
glb_json_type = io.read(4)
glb_json_data = io.read(glb_json_length);1
glb_buff_length = io.read(4).unpack("L*")[0]
glb_buff_type = io.read(4)
glb_buff_data = io.read(glb_buff_length);1
property=JSON.parse(glb_json_data)
# Create Data
image = open('testdata.png', 'rb').read;1
data = image + FF + FF + FF;1
property["bufferViews"].each_with_index do |x, i|
next if i==0
data += glb_buff_data[x["byteOffset"], x["byteLength"]];
# data += FF*4 if i==2
# data += FF*8 if i==4
end
data += FF;1
# Create JSON
property["images"][0]["name"]="testdata"
property["bufferViews"][0]["byteLength"] = image.size
xs = property["bufferViews"]
(1..xs.size-1).each do |i|
x = xs[i - 1]
offset = x["byteOffset"] + x["byteLength"]
offset += 3 if i==1
# offset += 4 if i==3
# offset += 8 if i==5
xs[i]["byteOffset"] = offset
end
property["buffers"][0]["byteLength"] = data.size
json = property.to_json.gsub('/', '\/') + " "
# Store GLTF
glb = GLB_H_MAGIC
glb += GLB_H_VERSION
glb += [json.size + data.size + 12 + 8 + 8].pack("L*")
glb += [json.size].pack("L*")
glb += GLB_JSON_TYPE
glb += json
glb += [data.size].pack("L*")
glb += GLB_BUFF_TYPE
glb += data;1
open('output.vci', 'wb') do |f|
f.write(glb)
end
{
"asset": {
"generator": "UniVCI-0.31",
"version": "2.0"
},
"buffers": [
{
"byteLength": 100012
}
],
"bufferViews": [
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 21697
},
{
"buffer": 0,
"byteOffset": 21697,
"byteLength": 4630
},
{
"buffer": 0,
"byteOffset": 26328,
"byteLength": 3840,
"target": 34962
},
{
"buffer": 0,
"byteOffset": 30168,
"byteLength": 3840,
"target": 34962
},
{
"buffer": 0,
"byteOffset": 34008,
"byteLength": 2560,
"target": 34962
},
{
"buffer": 0,
"byteOffset": 36568,
"byteLength": 5760,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 42328,
"byteLength": 384,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 42720,
"byteLength": 3900,
"target": 34962
},
{
"buffer": 0,
"byteOffset": 46620,
"byteLength": 3900,
"target": 34962
},
{
"buffer": 0,
"byteOffset": 50520,
"byteLength": 2600,
"target": 34962
},
{
"buffer": 0,
"byteOffset": 53120,
"byteLength": 5760,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 58880,
"byteLength": 360,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 59244,
"byteLength": 2712,
"target": 34962
},
{
"buffer": 0,
"byteOffset": 61956,
"byteLength": 2712,
"target": 34962
},
{
"buffer": 0,
"byteOffset": 64668,
"byteLength": 4608,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 69276,
"byteLength": 6732,
"target": 34962
},
{
"buffer": 0,
"byteOffset": 76008,
"byteLength": 6732,
"target": 34962
},
{
"buffer": 0,
"byteOffset": 82744,
"byteLength": 4488,
"target": 34962
},
{
"buffer": 0,
"byteOffset": 87232,
"byteLength": 11520,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 98752,
"byteLength": 1260
}
],
"accessors": [
{
"bufferView": 2,
"byteOffset": 0,
"type": "VEC3",
"componentType": 5126,
"count": 320,
"max": [
0.249999955,
0.5,
0.25000006
],
"min": [
-5.332548E-08,
0,
-0.25000006
],
"normalized": false
},
{
"bufferView": 3,
"byteOffset": 0,
"type": "VEC3",
"componentType": 5126,
"count": 320,
"normalized": false
},
{
"bufferView": 4,
"byteOffset": 0,
"type": "VEC2",
"componentType": 5126,
"count": 320,
"normalized": false
},
{
"bufferView": 5,
"byteOffset": 0,
"type": "SCALAR",
"componentType": 5125,
"count": 1440,
"normalized": false
},
{
"bufferView": 6,
"byteOffset": 0,
"type": "SCALAR",
"componentType": 5125,
"count": 96,
"normalized": false
},
{
"bufferView": 7,
"byteOffset": 0,
"type": "VEC3",
"componentType": 5126,
"count": 325,
"max": [
3.980678E-08,
0.5,
0.25000006
],
"min": [
-0.25000003,
0,
-0.25000006
],
"normalized": false
},
{
"bufferView": 8,
"byteOffset": 0,
"type": "VEC3",
"componentType": 5126,
"count": 325,
"normalized": false
},
{
"bufferView": 9,
"byteOffset": 0,
"type": "VEC2",
"componentType": 5126,
"count": 325,
"normalized": false
},
{
"bufferView": 10,
"byteOffset": 0,
"type": "SCALAR",
"componentType": 5125,
"count": 1440,
"normalized": false
},
{
"bufferView": 11,
"byteOffset": 0,
"type": "SCALAR",
"componentType": 5125,
"count": 90,
"normalized": false
},
{
"bufferView": 12,
"byteOffset": 0,
"type": "VEC3",
"componentType": 5126,
"count": 226,
"max": [
0.04094824,
0.8000002,
0.04094861
],
"min": [
-0.0409483574,
-1.39698442E-09,
-0.040948
],
"normalized": false
},
{
"bufferView": 13,
"byteOffset": 0,
"type": "VEC3",
"componentType": 5126,
"count": 226,
"normalized": false
},
{
"bufferView": 14,
"byteOffset": 0,
"type": "SCALAR",
"componentType": 5125,
"count": 1152,
"normalized": false
},
{
"bufferView": 15,
"byteOffset": 0,
"type": "VEC3",
"componentType": 5126,
"count": 561,
"max": [
0.249999955,
0.5,
0.25000006
],
"min": [
-0.25000003,
0,
-0.25000006
],
"normalized": false
},
{
"bufferView": 16,
"byteOffset": 0,
"type": "VEC3",
"componentType": 5126,
"count": 561,
"normalized": false
},
{
"bufferView": 17,
"byteOffset": 0,
"type": "VEC2",
"componentType": 5126,
"count": 561,
"normalized": false
},
{
"bufferView": 18,
"byteOffset": 0,
"type": "SCALAR",
"componentType": 5125,
"count": 2880,
"normalized": false
}
],
"textures": [
{
"sampler": 0,
"source": 0
},
{
"sampler": 1,
"source": 1
}
],
"samplers": [
{
"magFilter": 9729,
"minFilter": 9729,
"wrapS": 10497,
"wrapT": 10497
},
{
"magFilter": 9729,
"minFilter": 9729,
"wrapS": 10497,
"wrapT": 10497
}
],
"images": [
{
"name": "suikaTex_out",
"bufferView": 0,
"mimeType": "image\/png"
},
{
"name": "suikaTex_in",
"bufferView": 1,
"mimeType": "image\/png"
}
],
"materials": [
{
"name": "Suika.001",
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 0,
"texCoord": 0,
"extensions": {
"KHR_texture_transform": {
"offset": [
0,
0
],
"rotation": 0,
"scale": [
1,
1
],
"texCoord": 0
}
}
},
"baseColorFactor": [
0.6038274,
0.6038274,
0.6038274,
1
],
"metallicFactor": 0,
"roughnessFactor": 1
},
"alphaMode": "OPAQUE",
"alphaCutoff": 0.5,
"doubleSided": false
},
{
"name": "Suika_naka",
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 1,
"texCoord": 0,
"extensions": {
"KHR_texture_transform": {
"offset": [
0,
0
],
"rotation": 0,
"scale": [
1,
1
],
"texCoord": 0
}
}
},
"baseColorFactor": [
0.6038274,
0.6038274,
0.6038274,
1
],
"metallicFactor": 0,
"roughnessFactor": 1
},
"alphaMode": "OPAQUE",
"alphaCutoff": 0.5,
"doubleSided": false
},
{
"name": "Suika.001",
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 0,
"texCoord": 0,
"extensions": {
"KHR_texture_transform": {
"offset": [
0,
0
],
"rotation": 0,
"scale": [
1,
1
],
"texCoord": 0
}
}
},
"baseColorFactor": [
0.6038274,
0.6038274,
0.6038274,
1
],
"metallicFactor": 0,
"roughnessFactor": 1
},
"alphaMode": "OPAQUE",
"alphaCutoff": 0.5,
"doubleSided": false
},
{
"name": "Suika_naka",
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 1,
"texCoord": 0,
"extensions": {
"KHR_texture_transform": {
"offset": [
0,
0
],
"rotation": 0,
"scale": [
1,
1
],
"texCoord": 0
}
}
},
"baseColorFactor": [
0.6038274,
0.6038274,
0.6038274,
1
],
"metallicFactor": 0,
"roughnessFactor": 1
},
"alphaMode": "OPAQUE",
"alphaCutoff": 0.5,
"doubleSided": false
},
{
"name": "suikabou",
"pbrMetallicRoughness": {
"baseColorFactor": [
0.323017746,
0.0488309227,
0,
1
],
"metallicFactor": 0,
"roughnessFactor": 1
},
"alphaMode": "OPAQUE",
"alphaCutoff": 0.5,
"doubleSided": false
},
{
"name": "Suika",
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 0,
"texCoord": 0,
"extensions": {
"KHR_texture_transform": {
"offset": [
0,
0
],
"rotation": 0,
"scale": [
1,
1
],
"texCoord": 0
}
}
},
"baseColorFactor": [
0.6038274,
0.6038274,
0.6038274,
1
],
"metallicFactor": 0,
"roughnessFactor": 1
},
"alphaMode": "OPAQUE",
"alphaCutoff": 0.5,
"doubleSided": false
}
],
"meshes": [
{
"name": "Suika_wareL",
"primitives": [
{
"mode": 4,
"indices": 3,
"attributes": {
"POSITION": 0,
"NORMAL": 1,
"TEXCOORD_0": 2
},
"material": 0
},
{
"mode": 4,
"indices": 4,
"attributes": {
"POSITION": 0,
"NORMAL": 1,
"TEXCOORD_0": 2
},
"material": 1
}
]
},
{
"name": "Suika_wareR",
"primitives": [
{
"mode": 4,
"indices": 8,
"attributes": {
"POSITION": 5,
"NORMAL": 6,
"TEXCOORD_0": 7
},
"material": 2
},
{
"mode": 4,
"indices": 9,
"attributes": {
"POSITION": 5,
"NORMAL": 6,
"TEXCOORD_0": 7
},
"material": 3
}
]
},
{
"name": "Cylinder",
"primitives": [
{
"mode": 4,
"indices": 12,
"attributes": {
"POSITION": 10,
"NORMAL": 11
},
"material": 4
}
]
},
{
"name": "Suika",
"primitives": [
{
"mode": 4,
"indices": 16,
"attributes": {
"POSITION": 13,
"NORMAL": 14,
"TEXCOORD_0": 15
},
"material": 5
}
]
}
],
"nodes": [
{
"name": "Suika_wareL",
"translation": [
0,
-1,
0
],
"rotation": [
0,
0,
0,
1
],
"scale": [
1,
1,
1
],
"mesh": 0,
"extensions": {
"VCAST_vci_collider": {
"colliders": [
{
"type": "box",
"layer": "default",
"center": [
0.125,
0,
0
],
"shape": [
0.25,
0.5,
0.5000001
],
"grabable": false,
"useGravity": false,
"isTrigger": false
}
]
},
"VCAST_vci_rigidbody": {
"rigidbodies": [
{
"mass": 1,
"drag": 0,
"angularDrag": 0.05,
"useGravity": true,
"isKinematic": false,
"interpolate": "NONE",
"collisionDetection": "DISCRETE",
"freezePositionX": false,
"freezePositionY": false,
"freezePositionZ": false,
"freezeRotationX": false,
"freezeRotationY": false,
"freezeRotationZ": false
}
]
},
"VCAST_vci_item": {
"grabbable": true,
"scalable": false,
"uniformScaling": false,
"attractable": false,
"groupId": 1
}
},
"extras": {}
},
{
"name": "Suika_wareR",
"translation": [
0,
-1,
0
],
"rotation": [
0,
0,
0,
1
],
"scale": [
1,
1,
1
],
"mesh": 1,
"extensions": {
"VCAST_vci_collider": {
"colliders": [
{
"type": "box",
"layer": "default",
"center": [
-0.126,
0,
0
],
"shape": [
0.25000006,
0.5,
0.5000001
],
"grabable": false,
"useGravity": false,
"isTrigger": false
}
]
},
"VCAST_vci_rigidbody": {
"rigidbodies": [
{
"mass": 1,
"drag": 0,
"angularDrag": 0.05,
"useGravity": true,
"isKinematic": false,
"interpolate": "NONE",
"collisionDetection": "DISCRETE",
"freezePositionX": false,
"freezePositionY": false,
"freezePositionZ": false,
"freezeRotationX": false,
"freezeRotationY": false,
"freezeRotationZ": false
}
]
},
"VCAST_vci_item": {
"grabbable": true,
"scalable": false,
"uniformScaling": false,
"attractable": false,
"groupId": 1
}
},
"extras": {}
},
{
"name": "suika_bou",
"translation": [
0,
0.97,
0
],
"rotation": [
0,
0,
0,
1
],
"scale": [
1,
1,
1
],
"mesh": 2,
"extensions": {
"VCAST_vci_collider": {
"colliders": [
{
"type": "capsule",
"layer": "default",
"center": [
-5.96046448E-08,
0.4000001,
3.054738E-07
],
"shape": [
0.0409483053,
0.8000002,
1
],
"grabable": false,
"useGravity": false,
"isTrigger": false
}
]
},
"VCAST_vci_rigidbody": {
"rigidbodies": [
{
"mass": 1,
"drag": 0,
"angularDrag": 0.05,
"useGravity": false,
"isKinematic": true,
"interpolate": "NONE",
"collisionDetection": "DISCRETE",
"freezePositionX": false,
"freezePositionY": false,
"freezePositionZ": false,
"freezeRotationX": false,
"freezeRotationY": false,
"freezeRotationZ": false
}
]
},
"VCAST_vci_item": {
"grabbable": true,
"scalable": true,
"uniformScaling": false,
"attractable": false,
"groupId": 1
}
},
"extras": {}
},
{
"name": "Suika",
"translation": [
0,
0,
0
],
"rotation": [
0,
0,
0,
1
],
"scale": [
1,
1,
1
],
"mesh": 3,
"extensions": {
"VCAST_vci_collider": {
"colliders": [
{
"type": "sphere",
"layer": "default",
"center": [
-3.7252903E-08,
0.25,
0
],
"shape": [
0.25000006
],
"grabable": false,
"useGravity": false,
"isTrigger": false
}
]
},
"VCAST_vci_rigidbody": {
"rigidbodies": [
{
"mass": 1,
"drag": 0,
"angularDrag": 0.05,
"useGravity": true,
"isKinematic": false,
"interpolate": "NONE",
"collisionDetection": "DISCRETE",
"freezePositionX": false,
"freezePositionY": false,
"freezePositionZ": false,
"freezeRotationX": false,
"freezeRotationY": false,
"freezeRotationZ": false
}
]
},
"VCAST_vci_item": {
"grabbable": true,
"scalable": true,
"uniformScaling": false,
"attractable": false,
"groupId": 1
}
},
"extras": {}
}
],
"scene": 0,
"scenes": [
{
"nodes": [
0,
1,
2,
3
]
}
],
"extensionsUsed": [
"KHR_materials_unlit",
"KHR_texture_transform",
"VCAST_vci",
"VCAST_vci_animation",
"Effekseer",
"Effekseer_emitters",
"VCAST_vci_text",
"VCAST_vci_rectTransform",
"VCAST_vci_spring_bone",
"VCAST_vci_player_spawn_point",
"VCAST_vci_player_spawn_point_restriction",
"VCAST_vci_location_bounds",
"VCAST_vci_location_lighting",
"VCAST_vci_lightmap",
"VCAST_vci_reflectionProbe",
"VCAST_materials_pbr"
],
"extensions": {
"VCAST_vci_meta": {
"exporterVCIVersion": "UniVCI-0.31",
"specVersion": "0.31",
"title": "Suika VCI",
"version": "0.6",
"author": "koduki",
"contactInformation": "",
"reference": "",
"description": "",
"thumbnail": -1,
"modelDataLicenseType": "redistribution_prohibited",
"modelDataOtherLicenseUrl": "",
"scriptLicenseType": "redistribution_prohibited",
"scriptOtherLicenseUrl": "",
"scriptWriteProtected": false,
"scriptEnableDebugging": false,
"scriptFormat": "luaText"
},
"VCAST_vci_embedded_script": {
"scripts": [
{
"name": "main",
"mimeType": "x_application_lua",
"targetEngine": "moonsharp",
"source": 19
}
],
"entryPoint": 0
},
"VCAST_vci_material_unity": {
"materials": [
{
"name": "Suika.001",
"shader": "VRM_USE_GLTFSHADER",
"renderQueue": 2000,
"floatProperties": {},
"vectorProperties": {},
"textureProperties": {},
"keywordMap": {},
"tagMap": {}
},
{
"name": "Suika_naka",
"shader": "VRM_USE_GLTFSHADER",
"renderQueue": 2000,
"floatProperties": {},
"vectorProperties": {},
"textureProperties": {},
"keywordMap": {},
"tagMap": {}
},
{
"name": "Suika.001",
"shader": "VRM_USE_GLTFSHADER",
"renderQueue": 2000,
"floatProperties": {},
"vectorProperties": {},
"textureProperties": {},
"keywordMap": {},
"tagMap": {}
},
{
"name": "Suika_naka",
"shader": "VRM_USE_GLTFSHADER",
"renderQueue": 2000,
"floatProperties": {},
"vectorProperties": {},
"textureProperties": {},
"keywordMap": {},
"tagMap": {}
},
{
"name": "suikabou",
"shader": "VRM_USE_GLTFSHADER",
"renderQueue": 2000,
"floatProperties": {},
"vectorProperties": {},
"textureProperties": {},
"keywordMap": {},
"tagMap": {}
},
{
"name": "Suika",
"shader": "VRM_USE_GLTFSHADER",
"renderQueue": 2000,
"floatProperties": {},
"vectorProperties": {},
"textureProperties": {},
"keywordMap": {},
"tagMap": {}
}
]
}
},
"extras": {}
}
GLB_H_MAGIC = "glTF".b
GLB_H_VERSION = [2].pack("L*")
GLB_JSON_TYPE = "JSON".b
GLB_BUFF_TYPE = "BIN\x00".b
property=JSON.parse(glb_json_data)
property["asset"]["generator"]="ruby-vci:0.1123456"
json = property.to_json.gsub('/', '\/') + " "
data = glb_buff_data;1
glb = GLB_H_MAGIC
glb += GLB_H_VERSION
glb += [json.size + data.size + 12 + 8 + 8].pack("L*")
glb += [json.size].pack("L*")
glb += GLB_JSON_TYPE
glb += json
glb += [data.size].pack("L*")
glb += GLB_BUFF_TYPE
glb += data;1
open('output.vci', 'wb') do |f|
f.write(glb)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment