Skip to content

Commit

Permalink
Merge pull request #104 from rdeits/rd/meshcat-dae
Browse files Browse the repository at this point in the history
Update meshcat to get better .DAE handling
  • Loading branch information
rdeits authored Jul 1, 2019
2 parents aa56209 + cf3649d commit 3b5533f
Show file tree
Hide file tree
Showing 12 changed files with 359 additions and 35 deletions.
2 changes: 1 addition & 1 deletion deps/build.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ using Base.Filesystem
using BinDeps: unpack_cmd, download_cmd


const meshcat_sha = "efc157436ca4abf5706cf248c51d2e86cae1a0f0"
const meshcat_sha = "3122cecd5da022ad96bb0c8dc0f811a1bc492350"
const meshcat_url = "https://github.com/rdeits/meshcat/archive/$meshcat_sha.zip"

const assets_dir = normpath(joinpath(@__DIR__, "..", "assets"))
Expand Down
5 changes: 5 additions & 0 deletions src/MeshCat.jl
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export Object,
Triad,
Mesh,
MeshFileGeometry,
MeshFileObject,
Points,
Line,
LineLoop,
Expand All @@ -66,8 +67,12 @@ export Animation,

export ArrowVisualizer

abstract type AbstractObject end
abstract type AbstractMaterial end

include("trees.jl")
using .SceneTrees
include("mesh_files.jl")
include("geometry.jl")
include("objects.jl")
include("animations.jl")
Expand Down
21 changes: 0 additions & 21 deletions src/geometry.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,6 @@ center(geometry::HyperSphere) = origin(geometry)
center(geometry::Cylinder) = (origin(geometry) + geometry.extremity) / 2
center(geometry::Cone) = (origin(geometry) + geometry.apex) / 2

"""
An MeshFileGeometry represents a mesh which is stored as the raw contents
of a file, rather than as a collection of points and vertices. This is useful for
transparently passing mesh files which we can't load in Julia directly to meshcat.
"""
struct MeshFileGeometry{S <: Union{String, Vector{UInt8}}}
contents::S
format::String
end

function MeshFileGeometry(filename)
ext = lowercase(splitext(filename)[2])
if ext (".obj", ".dae")
MeshFileGeometry(open(f -> read(f, String), filename), ext[2:end])
elseif ext == ".stl"
MeshFileGeometry(open(read, filename), ext[2:end])
else
throw(ArgumentError("Unsupported extension: $ext. Only .obj, .dae, and .stl meshes can be used to construct MeshFileGeometry"))
end
end


"""
$(SIGNATURES)
Expand Down
19 changes: 17 additions & 2 deletions src/lowering.jl
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,26 @@ end
function lower(geom::MeshFileGeometry)
Dict{String, Any}(
"uuid" => string(uuid1()),
"type" => "_meshfile",
"type" => "_meshfile_geometry",
"format" => geom.format,
"data" => pack_mesh_file_data(geom.contents))
end

function lower(obj::MeshFileObject)
Dict{String, Any}(
"metadata" => Dict{String, Any}("version" => 4.5, "type" => "Object"),
"geometries" => [],
"materials" => [],
"object" => Dict{String, Any}(
"uuid" => string(uuid1()),
"type" => "_meshfile_object",
"format" => obj.format,
"data" => pack_mesh_file_data(obj.contents),
"mtl_library" => obj.mtl_library,
"resources" => obj.resources))
end


# TODO: Unify these two methods once https://github.com/rdeits/meshcat/issues/50 is resolved
pack_mesh_file_data(s::AbstractString) = s
pack_mesh_file_data(s::AbstractVector{UInt8}) = PackedVector(s)
Expand Down Expand Up @@ -228,7 +243,7 @@ end
function lower(img::PngImage)
Dict{String, Any}(
"uuid" => string(uuid1()),
"url" => string("data:image/png;base64,", base64encode(img.data))
"url" => data_uri(img.data)
)
end

Expand Down
114 changes: 114 additions & 0 deletions src/mesh_files.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@

"""
An MeshFileGeometry represents a mesh which is stored as the raw contents
of a file, rather than as a collection of points and vertices. This is useful for
transparently passing mesh files which we can't load in Julia directly to meshcat.
Supported formats:
* .stl (ASCII and binary)
* .obj
* .dae (Collada)
For .obj and .dae files, only a single mesh geometry will be loaded, and any
material or texture properties will be ignored. To load an entire collection of
objects (complete with materials and textures) from an .obj or .dae file, see
MeshFileObject instead.
"""
struct MeshFileGeometry{S <: Union{String, Vector{UInt8}}}
contents::S
format::String
end

function MeshFileGeometry(filename)
ext = lowercase(splitext(filename)[2])
if ext (".obj", ".dae")
MeshFileGeometry(open(f -> read(f, String), filename), ext[2:end])
elseif ext == ".stl"
MeshFileGeometry(open(read, filename), ext[2:end])
else
throw(ArgumentError("Unsupported extension: $ext. Only .obj, .dae, and .stl meshes can be used to construct MeshFileGeometry"))
end
end


"""
A MeshFileObject is similar to a MeshFileGeometry, but rather than representing
a single geometry, it supports loading an entire object (with geometries,
materials, and textures), or even a collection of objects (for supported file
types).
Supported formats:
* .obj
* .dae (Collada)
Since mesh files may include references to other files (such as texture images),
the MeshFileObject also includes a `resources` dictionary mapping relative paths
(as specified in the mesh file) to the contents of the referenced files. Those contents are specified using data URIs, e.g.:
data:image/png;base64,<base-64 encoded data here>
If you construct a MeshFileObject from a `.dae` or `.obj` file, the resources
dictionary will automatically be populated.
"""
struct MeshFileObject <: AbstractObject
contents::String
format::String
mtl_library::Union{String, Nothing}
resources::Dict{String, String}
end

function MeshFileObject(filename)
ext = lowercase(splitext(filename)[2])
if ext (".obj", ".dae")
throw(ArgumentError("Unsupported extension: $ext. Only .obj and .dae meshes can be used to construct MeshFileObject"))
end
contents = open(f -> read(f, String), filename)
format = ext[2:end]
if ext == ".obj"
mtl_library = load_mtl_library(contents, dirname(filename))
resources = load_mtl_textures(mtl_library, dirname(filename))
else
mtl_library = nothing
resources = load_dae_textures(contents, dirname(filename))
end
MeshFileObject(contents, format, mtl_library, resources)
end

function load_mtl_library(obj_contents, directory=".")
libraries = String[]
for line in eachline(IOBuffer(obj_contents))
m = match(r"^mtllib (.*)$", line)
if m !== nothing
push!(libraries, open(f -> read(f, String), joinpath(directory, m.captures[1])))
end
end
join(libraries, '\n')
end

data_uri(contents::Vector{UInt8}) = string("data:image/png;base64,", base64encode(contents))

function load_mtl_textures(mtl_contents, directory=".")
textures = Dict{String, String}()
for line in eachline(IOBuffer(mtl_contents))
m = match(r"^map_[^ ]* (.*)$", line)
if m !== nothing
name = m.captures[1]
contents = open(read, joinpath(directory, name))
textures[name] = data_uri(contents)
end
end
textures
end

function load_dae_textures(dae_contents, directory=".")
# TODO: this is probably not a very robust parsing strategy,
# but I don't have enough examples to work from to decide on a
# better one, so I'm keeping it simple.
r = r"\<image [^\>]*\>\s*<init_from\>([^\<]*)\<\/init_from\>"m
textures = Dict{String, String}()
for match in eachmatch(r, dae_contents)
name = match.captures[1]
textures[name] = data_uri(open(read, joinpath(directory, name)))
end
textures
end
3 changes: 1 addition & 2 deletions src/objects.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
const GeometryLike = Union{AbstractGeometry, AbstractMesh, MeshFileGeometry}
abstract type AbstractObject end
abstract type AbstractMaterial end

struct Object{G <: GeometryLike, M <: AbstractMaterial} <: AbstractObject
geometry::G
Expand Down Expand Up @@ -76,3 +74,4 @@ LineBasicMaterial(;kw...) = GenericMaterial(_type="LineBasicMaterial"; kw...)
size::Float32 = 0.002
vertexColors::Int = 2
end

148 changes: 148 additions & 0 deletions test/data/meshes/cube.dae
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.1">
<asset>
<contributor>
<author>VCGLab</author>
<authoring_tool>VCGLib | MeshLab</authoring_tool>
</contributor>
<up_axis>Y_UP</up_axis>
<created>Tue May 28 03:20:42 2019</created>
<modified>Tue May 28 03:20:42 2019</modified>
</asset>
<library_images>
<image id="texture0" name="texture0">
<init_from>cube.png</init_from>
</image>
</library_images>
<library_materials>
<material id="material0" name="material0">
<instance_effect url="#material0-fx"/>
</material>
</library_materials>
<library_effects>
<effect id="material0-fx">
<profile_COMMON>
<newparam sid="texture0-surface">
<surface type="2D">
<init_from>texture0</init_from>
<format>A8R8G8B8</format>
</surface>
</newparam>
<newparam sid="texture0-sampler">
<sampler2D>
<source>texture0-surface</source>
<minfilter>LINEAR</minfilter>
<magfilter>LINEAR</magfilter>
</sampler2D>
</newparam>
<technique sid="common">
<blinn>
<emission>
<color>0 0 0 1</color>
</emission>
<ambient>
<color>0 0 0 1</color>
</ambient>
<diffuse>
<texture texture="texture0" texcoord="UVSET0"/>
</diffuse>
<specular>
<color>0 0 0 1</color>
</specular>
<shininess>
<float>0.3</float>
</shininess>
<reflective>
<color>0 0 0 1</color>
</reflective>
<reflectivity>
<float>0.5</float>
</reflectivity>
<transparent>
<color>0 0 0 1</color>
</transparent>
<transparency>
<float>0</float>
</transparency>
<index_of_refraction>
<float>0</float>
</index_of_refraction>
</blinn>
</technique>
</profile_COMMON>
</effect>
</library_effects>
<library_geometries>
<geometry id="shape0-lib" name="shape0">
<mesh>
<source id="shape0-lib-positions" name="position">
<float_array id="shape0-lib-positions-array" count="24">0 0 0 1 0 0 1 1 0 0 1 0 0 0 1 1 0 1 1 1 1 0 1 1</float_array>
<technique_common>
<accessor count="8" source="#shape0-lib-positions-array" stride="3">
<param name="X" type="float"/>
<param name="Y" type="float"/>
<param name="Z" type="float"/>
</accessor>
</technique_common>
</source>
<source id="shape0-lib-normals" name="normal">
<float_array id="shape0-lib-normals-array" count="36">0 0 -1 0 0 -1 0 0 1 0 0 1 0 -1 0 0 -1 0 1 0 0 1 0 0 0 1 0 0 1 0 -1 0 0 -1 0 0</float_array>
<technique_common>
<accessor count="12" source="#shape0-lib-normals-array" stride="3">
<param name="X" type="float"/>
<param name="Y" type="float"/>
<param name="Z" type="float"/>
</accessor>
</technique_common>
</source>
<source id="shape0-lib-vcolor" name="vcolor">
<float_array id="shape0-lib-vcolor-array" count="24">1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1</float_array>
<technique_common>
<accessor count="8" source="#shape0-lib-vcolor-array" stride="3">
<param name="R" type="float"/>
<param name="G" type="float"/>
<param name="B" type="float"/>
</accessor>
</technique_common>
</source>
<source id="shape0-lib-map" name="map">
<float_array id="shape0-lib-map-array" count="72">0.3 0.375 0.3 0.375 0.3 0.375 0.3 0.375 0.3 0.375 0.3 0.375 0.3 0.875 0.3 0.875 0.3 0.875 0.3 0.875 0.3 0.875 0.3 0.875 0.3 0.625 0.3 0.625 0.3 0.625 0.3 0.625 0.3 0.625 0.3 0.625 0.6 0.625 0.6 0.625 0.6 0.625 0.6 0.625 0.6 0.625 0.6 0.625 0.3 0.125 0.3 0.125 0.3 0.125 0.3 0.125 0.3 0.125 0.3 0.125 0.2 0.625 0.2 0.625 0.2 0.625 0.2 0.625 0.2 0.625 0.2 0.625</float_array>
<technique_common>
<accessor count="36" source="#shape0-lib-map-array" stride="2">
<param name="U" type="float"/>
<param name="V" type="float"/>
</accessor>
</technique_common>
</source>
<vertices id="shape0-lib-vertices">
<input semantic="POSITION" source="#shape0-lib-positions"/>
</vertices>
<triangles count="12" material="material0">
<input offset="0" semantic="VERTEX" source="#shape0-lib-vertices"/>
<input offset="1" semantic="COLOR" source="#shape0-lib-vcolor"/>
<input offset="2" semantic="NORMAL" source="#shape0-lib-normals"/>
<input offset="3" semantic="TEXCOORD" source="#shape0-lib-map"/>
<p>2 2 0 0 1 1 0 1 0 0 0 2 0 0 1 3 3 3 1 4 2 2 1 5 4 4 2 6 5 5 2 7 6 6 2 8 6 6 3 9 7 7 3 10 4 4 3 11 0 0 4 12 1 1 4 13 5 5 4 14 5 5 5 15 4 4 5 16 0 0 5 17 1 1 6 18 2 2 6 19 6 6 6 20 6 6 7 21 5 5 7 22 1 1 7 23 2 2 8 24 3 3 8 25 7 7 8 26 7 7 9 27 6 6 9 28 2 2 9 29 3 3 10 30 0 0 10 31 4 4 10 32 4 4 11 33 7 7 11 34 3 3 11 35</p>
</triangles>
</mesh>
</geometry>
</library_geometries>
<library_visual_scenes>
<visual_scene id="VisualSceneNode" name="VisualScene">
<node id="node" name="node">
<instance_geometry url="#shape0-lib">
<bind_material>
<technique_common>
<instance_material symbol="material0" target="#material0">
<bind_vertex_input semantic="UVSET0" input_semantic="TEXCOORD"/>
</instance_material>
</technique_common>
</bind_material>
</instance_geometry>
</node>
</visual_scene>
</library_visual_scenes>
<scene>
<instance_visual_scene url="#VisualSceneNode"/>
</scene>
</COLLADA>
8 changes: 8 additions & 0 deletions test/data/meshes/cube.mtl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
newmtl material_0
Ka 1.0 1.0 1.0
Kd 1.000000 1.000000 1.000000
Ks 1.000000 1.000000 1.000000
Tr 0.000000
illum 2
Ns 0.000000
map_Kd cube.png
Loading

0 comments on commit 3b5533f

Please sign in to comment.