diff --git a/src/core/p5.Renderer3D.js b/src/core/p5.Renderer3D.js index 455ec6d59c..5b1a365081 100644 --- a/src/core/p5.Renderer3D.js +++ b/src/core/p5.Renderer3D.js @@ -148,6 +148,10 @@ export class Renderer3D extends Renderer { this.states.drawMode = constants.FILL; this.states._tex = null; + this.states._specularTex = null; + this.states._ambientTex = null; + this.states._shininessTex = null; + this.states._normalTex = null; this.states.textureMode = constants.IMAGE; this.states.textureWrapX = constants.CLAMP; this.states.textureWrapY = constants.CLAMP; @@ -611,7 +615,10 @@ export class Renderer3D extends Renderer { state.texture != null || state.ambientColor != null || state.specularColor != null || - state.shininess != null); + state.shininess != null || + state.specularTexture != null || + state.ambientTexture != null || + state.shininessTexture != null); if (hasMaterial) { this.push(); this._applyPartState(state); @@ -676,13 +683,29 @@ export class Renderer3D extends Renderer { this.states.setValue('curAmbientColor', partState.ambientColor); this.states.setValue('_hasSetAmbient', true); } + if (partState.ambientTexture) { + // an ambient map modulates the ambient term, so make sure it is on + this.states.setValue('_ambientTex', partState.ambientTexture); + this.states.setValue('_hasSetAmbient', true); + } if (partState.specularColor) { this.states.setValue('curSpecularColor', partState.specularColor); this.states.setValue('_useSpecularMaterial', true); } + if (partState.specularTexture) { + // a specular map modulates the specular term, so make sure that term is on + this.states.setValue('_specularTex', partState.specularTexture); + this.states.setValue('_useSpecularMaterial', true); + } if (partState.shininess != null) { this.states.setValue('_useShininess', partState.shininess); } + if (partState.shininessTexture) { + this.states.setValue('_shininessTex', partState.shininessTexture); + } + if (partState.normalTexture) { + this.states.setValue('_normalTex', partState.normalTexture); + } } _drawStrokes(geometry, { count } = {}) { @@ -1454,14 +1477,27 @@ export class Renderer3D extends Renderer { else if (this.states._useNormalMaterial) { return this._getNormalShader(); } - // Use light shader if lighting or textures are enabled + // Use light shader if lighting or textures are enabled. only reach for the + // heavier map variant when the current part actually binds an mtl map. else if (this.states.enableLighting || this.states._tex) { - return this._getLightShader(); + return this._getLightShader(this._hasActiveTextureMap()); } // Default to color shader if no other conditions are met return this._getColorShader(); } + // true when the current material has any mtl texture map bound (specular, + // ambient, shininess or normal). drives both shader-variant selection and + // whether the map uniforms are worth binding at all. + _hasActiveTextureMap() { + return !!( + this.states._specularTex || + this.states._ambientTex || + this.states._shininessTex || + this.states._normalTex + ); + } + baseMaterialShader() { return this._getLightShader(); } @@ -1567,6 +1603,28 @@ export class Renderer3D extends Renderer { fillShader.setUniform('uSampler', this.states._tex || empty); } this._settingFillUniforms = false; + // mtl texture maps only exist in the USE_TEXTURE_MAPS shader variant, which + // is only selected when a part actually binds one. so for the common case + // (lit scene, no maps) we skip all of these setUniform calls entirely and + // the plain phong shader carries none of the map uniforms. bind all four + // pairs together since the variant declares all of them. + if (this._hasActiveTextureMap()) { + // specular map (map_Ks) + fillShader.setUniform('uHasSpecularTex', !!this.states._specularTex); + fillShader.setUniform('uSpecularSampler', this.states._specularTex || empty); + // ambient map (map_Ka) + fillShader.setUniform('uHasAmbientTex', !!this.states._ambientTex); + fillShader.setUniform('uAmbientSampler', this.states._ambientTex || empty); + // shininess map (map_Ns): scales base shininess by the map's red channel + fillShader.setUniform('uHasShininessTex', !!this.states._shininessTex); + fillShader.setUniform( + 'uShininessSampler', + this.states._shininessTex || empty + ); + // normal map (map_Bump): perturbs the surface normal in tangent space + fillShader.setUniform('uHasNormalMap', !!this.states._normalTex); + fillShader.setUniform('uNormalSampler', this.states._normalTex || empty); + } fillShader.setUniform( 'uTint', this.states.tint?._getRGBA([255, 255, 255, 255]) ?? [255, 255, 255, 255] diff --git a/src/webgl/loading.js b/src/webgl/loading.js index f9a546f03b..be0f4bee6f 100755 --- a/src/webgl/loading.js +++ b/src/webgl/loading.js @@ -74,6 +74,9 @@ function parseMtlData(data) { } else if (tokens[0] === 'map_Ks') { //specular texture materials[currentMaterial].specularTexturePath = tokens[1]; + } else if (tokens[0] === 'map_Ns') { + //shininess texture + materials[currentMaterial].shininessTexturePath = tokens[1]; } else if (tokens[0] === 'map_Bump' || tokens[0] === 'bump') { //bump map. -bm etc can precede the path so take the last token. parsed //but not used until the renderer handles it. @@ -98,13 +101,39 @@ function mtlToPartState(material) { if (material.specularColor) state.specularColor = material.specularColor; if (material.shininess !== undefined) state.shininess = material.shininess; if (material.texture) state.texture = material.texture; + if (material.specularTexture) { + state.specularTexture = material.specularTexture; + // a specular map modulates a base specular colour; default to white so the + // map shows even when the mtl has a map_Ks but no explicit Ks colour. + if (!state.specularColor) state.specularColor = [1, 1, 1]; + } + if (material.ambientTexture) { + state.ambientTexture = material.ambientTexture; + // same idea as the specular map: default the base ambient colour to white + if (!state.ambientColor) state.ambientColor = [1, 1, 1]; + } + if (material.shininessTexture) { + state.shininessTexture = material.shininessTexture; + // the map scales the base shininess; default the base to 1 when no Ns + if (state.shininess == null) state.shininess = 1; + } + if (material.normalTexture) state.normalTexture = material.normalTexture; return state; } -// load each material's diffuse texture (map_Kd) and hang it on the material so -// it lands on the part state. paths resolve relative to the model file, a -// texture that fails just gets skipped. no-op if there's no loadImage. only -// map_Kd for now since that's all the renderer can use. +// each texture map the renderer can use: the parsed path field on the material, +// and the image field we hang the loaded p5.Image on for mtlToPartState to read. +const MATERIAL_TEXTURE_MAPS = [ + ['texturePath', 'texture'], // map_Kd (diffuse) + ['specularTexturePath', 'specularTexture'], // map_Ks (specular) + ['ambientTexturePath', 'ambientTexture'], // map_Ka (ambient) + ['shininessTexturePath', 'shininessTexture'], // map_Ns (shininess) + ['bumpTexturePath', 'normalTexture'] // map_Bump (normal) +]; + +// load each material's texture maps and hang them on the material so they land +// on the part state. paths resolve relative to the model file, a texture that +// fails just gets skipped. no-op if there's no loadImage. async function loadMaterialTextures(materials, modelPath, instance) { if (!instance || typeof instance.loadImage !== 'function') return; @@ -115,18 +144,20 @@ async function loadMaterialTextures(materials, modelPath, instance) { const jobs = []; for (const name in materials) { const material = materials[name]; - if (!material.texturePath) continue; - const url = resolve(material.texturePath); - jobs.push( - instance - .loadImage(url) - .then(img => { - material.texture = img; - }) - .catch(() => { - console.warn(`Texture not found, skipping: ${url}`); - }) - ); + for (const [pathField, imageField] of MATERIAL_TEXTURE_MAPS) { + if (!material[pathField]) continue; + const url = resolve(material[pathField]); + jobs.push( + instance + .loadImage(url) + .then(img => { + material[imageField] = img; + }) + .catch(() => { + console.warn(`Texture not found, skipping: ${url}`); + }) + ); + } } await Promise.all(jobs); diff --git a/src/webgl/p5.GeometryPart.js b/src/webgl/p5.GeometryPart.js index 7afa6e152a..3c3099a927 100644 --- a/src/webgl/p5.GeometryPart.js +++ b/src/webgl/p5.GeometryPart.js @@ -13,7 +13,11 @@ function createPartState() { ambientColor: null, // Ka -> [r, g, b] | null, each 0..1 specularColor: null, // Ks -> [r, g, b] | null, each 0..1 shininess: null, // Ns -> number | null - texture: null // map_Kd -> p5.Image | null + texture: null, // map_Kd -> p5.Image | null + specularTexture: null, // map_Ks -> p5.Image | null + ambientTexture: null, // map_Ka -> p5.Image | null + shininessTexture: null, // map_Ns -> p5.Image | null + normalTexture: null // map_Bump -> p5.Image | null }; } diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index dbdbe3c201..cffb0f5f83 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -731,14 +731,22 @@ class RendererGL extends Renderer3D { return super.baseMaterialShader(); } - _getLightShader() { - if (!this._defaultLightShader) { + // useTextureMaps compiles a second phong variant with the mtl texture-map + // code switched on via a #define. plain lit scenes keep getting the original + // shader with none of that compiled in, so they pay nothing for the feature. + _getLightShader(useTextureMaps = false) { + const cacheKey = useTextureMaps + ? '_defaultLightShaderWithMaps' + : '_defaultLightShader'; + if (!this[cacheKey]) { if (this._pInst._glAttributes.perPixelLighting) { - this._defaultLightShader = new Shader( + const mapsDefine = useTextureMaps ? '#define USE_TEXTURE_MAPS\n' : ''; + this[cacheKey] = new Shader( this, this._webGL2CompatibilityPrefix('vert', 'highp') + defaultShaders.phongVert, this._webGL2CompatibilityPrefix('frag', 'highp') + + mapsDefine + defaultShaders.phongFrag, { vertex: { @@ -767,7 +775,9 @@ class RendererGL extends Renderer3D { } ); } else { - this._defaultLightShader = new Shader( + // the non-per-pixel path uses lightTextureFrag, which has no map code, + // so both cache keys just resolve to the same plain shader here. + this[cacheKey] = new Shader( this, this._webGL2CompatibilityPrefix('vert', 'highp') + defaultShaders.lightVert, @@ -777,7 +787,7 @@ class RendererGL extends Renderer3D { } } - return this._defaultLightShader; + return this[cacheKey]; } _getNormalShader() { diff --git a/src/webgl/shaders/phong.frag b/src/webgl/shaders/phong.frag index 47ec519d47..1b132082f5 100644 --- a/src/webgl/shaders/phong.frag +++ b/src/webgl/shaders/phong.frag @@ -11,6 +11,21 @@ uniform vec4 uTint; uniform sampler2D uSampler; uniform bool isTexture; +// the mtl texture maps only exist in the USE_TEXTURE_MAPS variant of this +// shader. models that don't use any map get the plain phong shader instead, +// so none of these samplers/branches are even compiled in for them. dave's +// call: separate variant rather than branching in one shader that everyone pays for. +#ifdef USE_TEXTURE_MAPS +uniform sampler2D uSpecularSampler; +uniform bool uHasSpecularTex; +uniform sampler2D uAmbientSampler; +uniform bool uHasAmbientTex; +uniform sampler2D uShininessSampler; +uniform bool uHasShininessTex; +uniform sampler2D uNormalSampler; +uniform bool uHasNormalMap; +#endif + IN vec3 vNormal; IN vec2 vTexCoord; IN vec3 vViewPosition; @@ -39,11 +54,40 @@ struct Inputs { float metalness; }; +// build a tangent frame (TBN) from screen-space derivatives, so normal mapping +// needs no per-vertex tangents. p is the view-space position, uv the texture +// coords. based on christian schuller's derivative-based tangent method. this +// uses dFdx/dFdy, which are webgl2 only, so normal mapping is a webgl2 feature. +#if defined(USE_TEXTURE_MAPS) && defined(WEBGL2) +mat3 cotangentFrame(vec3 N, vec3 p, vec2 uv) { + vec3 dp1 = dFdx(p); + vec3 dp2 = dFdy(p); + vec2 duv1 = dFdx(uv); + vec2 duv2 = dFdy(uv); + vec3 dp2perp = cross(dp2, N); + vec3 dp1perp = cross(N, dp1); + vec3 T = dp2perp * duv1.x + dp1perp * duv2.x; + vec3 B = dp2perp * duv1.y + dp1perp * duv2.y; + float invmax = inversesqrt(max(dot(T, T), dot(B, B))); + return mat3(T * invmax, B * invmax, N); +} +#endif + void main(void) { HOOK_beforeFragment(); Inputs inputs; - inputs.normal = normalize(vNormal); + vec3 N = normalize(vNormal); +#if defined(USE_TEXTURE_MAPS) && defined(WEBGL2) + if (uHasNormalMap) { + // perturb the normal using a tangent frame derived per pixel, so nothing + // extra is uploaded per vertex for models that don't use a normal map. + vec3 mapN = TEXTURE(uNormalSampler, vTexCoord).rgb * 2.0 - 1.0; + mat3 TBN = cotangentFrame(N, vViewPosition, vTexCoord); + N = normalize(TBN * mapN); + } +#endif + inputs.normal = N; inputs.texCoord = vTexCoord; inputs.ambientLight = uAmbientColor; inputs.color = isTexture @@ -54,10 +98,24 @@ void main(void) { // so hooks users don't have to think about premultiplied alpha. inputs.color.rgb /= inputs.color.a; } - inputs.shininess = uShininess; inputs.metalness = uMetallic; +#ifdef USE_TEXTURE_MAPS + // map variant: shininess/ambient/specular can be modulated by their maps + inputs.shininess = uHasShininessTex + ? uShininess * TEXTURE(uShininessSampler, vTexCoord).r + : uShininess; + inputs.ambientMaterial = uHasAmbientTex + ? TEXTURE(uAmbientSampler, vTexCoord).rgb * uAmbientMatColor.rgb + : (uHasSetAmbient ? uAmbientMatColor.rgb : inputs.color.rgb); + inputs.specularMaterial = uHasSpecularTex + ? TEXTURE(uSpecularSampler, vTexCoord).rgb * uSpecularMatColor.rgb + : uSpecularMatColor.rgb; +#else + // default variant: plain phong, exactly as before this feature existed + inputs.shininess = uShininess; inputs.ambientMaterial = uHasSetAmbient ? uAmbientMatColor.rgb : inputs.color.rgb; inputs.specularMaterial = uSpecularMatColor.rgb; +#endif inputs.emissiveMaterial = uEmissiveMatColor.rgb; inputs = HOOK_getPixelInputs(inputs); diff --git a/test/unit/assets/bump_sphere.mtl b/test/unit/assets/bump_sphere.mtl new file mode 100644 index 0000000000..915d6493b5 --- /dev/null +++ b/test/unit/assets/bump_sphere.mtl @@ -0,0 +1,10 @@ +newmtl m0 +Kd 0.8 0.8 0.8 +Ks 0.5 0.5 0.5 +Ns 60 +map_Bump spheremap.jpg + +newmtl m1 +Kd 0.8 0.8 0.8 +Ks 0.5 0.5 0.5 +Ns 60 diff --git a/test/unit/assets/bump_sphere.obj b/test/unit/assets/bump_sphere.obj new file mode 100644 index 0000000000..71701f7fd6 --- /dev/null +++ b/test/unit/assets/bump_sphere.obj @@ -0,0 +1,1382 @@ +mtllib bump_sphere.mtl +v 0.0000 1.0000 0.0000 +v 0.0000 1.0000 0.0000 +v 0.0000 1.0000 0.0000 +v 0.0000 1.0000 0.0000 +v 0.0000 1.0000 0.0000 +v 0.0000 1.0000 0.0000 +v 0.0000 1.0000 0.0000 +v 0.0000 1.0000 0.0000 +v 0.0000 1.0000 0.0000 +v 0.0000 1.0000 0.0000 +v 0.0000 1.0000 0.0000 +v 0.0000 1.0000 0.0000 +v 0.0000 1.0000 0.0000 +v 0.0000 1.0000 0.0000 +v 0.0000 1.0000 0.0000 +v 0.0000 1.0000 0.0000 +v 0.0000 1.0000 0.0000 +v 0.1951 0.9808 0.0000 +v 0.1802 0.9808 0.0747 +v 0.1379 0.9808 0.1379 +v 0.0747 0.9808 0.1802 +v 0.0000 0.9808 0.1951 +v -0.0747 0.9808 0.1802 +v -0.1379 0.9808 0.1379 +v -0.1802 0.9808 0.0747 +v -0.1951 0.9808 0.0000 +v -0.1802 0.9808 -0.0747 +v -0.1379 0.9808 -0.1379 +v -0.0747 0.9808 -0.1802 +v -0.0000 0.9808 -0.1951 +v 0.0747 0.9808 -0.1802 +v 0.1379 0.9808 -0.1379 +v 0.1802 0.9808 -0.0747 +v 0.1951 0.9808 -0.0000 +v 0.3827 0.9239 0.0000 +v 0.3536 0.9239 0.1464 +v 0.2706 0.9239 0.2706 +v 0.1464 0.9239 0.3536 +v 0.0000 0.9239 0.3827 +v -0.1464 0.9239 0.3536 +v -0.2706 0.9239 0.2706 +v -0.3536 0.9239 0.1464 +v -0.3827 0.9239 0.0000 +v -0.3536 0.9239 -0.1464 +v -0.2706 0.9239 -0.2706 +v -0.1464 0.9239 -0.3536 +v -0.0000 0.9239 -0.3827 +v 0.1464 0.9239 -0.3536 +v 0.2706 0.9239 -0.2706 +v 0.3536 0.9239 -0.1464 +v 0.3827 0.9239 -0.0000 +v 0.5556 0.8315 0.0000 +v 0.5133 0.8315 0.2126 +v 0.3928 0.8315 0.3928 +v 0.2126 0.8315 0.5133 +v 0.0000 0.8315 0.5556 +v -0.2126 0.8315 0.5133 +v -0.3928 0.8315 0.3928 +v -0.5133 0.8315 0.2126 +v -0.5556 0.8315 0.0000 +v -0.5133 0.8315 -0.2126 +v -0.3928 0.8315 -0.3928 +v -0.2126 0.8315 -0.5133 +v -0.0000 0.8315 -0.5556 +v 0.2126 0.8315 -0.5133 +v 0.3928 0.8315 -0.3928 +v 0.5133 0.8315 -0.2126 +v 0.5556 0.8315 -0.0000 +v 0.7071 0.7071 0.0000 +v 0.6533 0.7071 0.2706 +v 0.5000 0.7071 0.5000 +v 0.2706 0.7071 0.6533 +v 0.0000 0.7071 0.7071 +v -0.2706 0.7071 0.6533 +v -0.5000 0.7071 0.5000 +v -0.6533 0.7071 0.2706 +v -0.7071 0.7071 0.0000 +v -0.6533 0.7071 -0.2706 +v -0.5000 0.7071 -0.5000 +v -0.2706 0.7071 -0.6533 +v -0.0000 0.7071 -0.7071 +v 0.2706 0.7071 -0.6533 +v 0.5000 0.7071 -0.5000 +v 0.6533 0.7071 -0.2706 +v 0.7071 0.7071 -0.0000 +v 0.8315 0.5556 0.0000 +v 0.7682 0.5556 0.3182 +v 0.5879 0.5556 0.5879 +v 0.3182 0.5556 0.7682 +v 0.0000 0.5556 0.8315 +v -0.3182 0.5556 0.7682 +v -0.5879 0.5556 0.5879 +v -0.7682 0.5556 0.3182 +v -0.8315 0.5556 0.0000 +v -0.7682 0.5556 -0.3182 +v -0.5879 0.5556 -0.5879 +v -0.3182 0.5556 -0.7682 +v -0.0000 0.5556 -0.8315 +v 0.3182 0.5556 -0.7682 +v 0.5879 0.5556 -0.5879 +v 0.7682 0.5556 -0.3182 +v 0.8315 0.5556 -0.0000 +v 0.9239 0.3827 0.0000 +v 0.8536 0.3827 0.3536 +v 0.6533 0.3827 0.6533 +v 0.3536 0.3827 0.8536 +v 0.0000 0.3827 0.9239 +v -0.3536 0.3827 0.8536 +v -0.6533 0.3827 0.6533 +v -0.8536 0.3827 0.3536 +v -0.9239 0.3827 0.0000 +v -0.8536 0.3827 -0.3536 +v -0.6533 0.3827 -0.6533 +v -0.3536 0.3827 -0.8536 +v -0.0000 0.3827 -0.9239 +v 0.3536 0.3827 -0.8536 +v 0.6533 0.3827 -0.6533 +v 0.8536 0.3827 -0.3536 +v 0.9239 0.3827 -0.0000 +v 0.9808 0.1951 0.0000 +v 0.9061 0.1951 0.3753 +v 0.6935 0.1951 0.6935 +v 0.3753 0.1951 0.9061 +v 0.0000 0.1951 0.9808 +v -0.3753 0.1951 0.9061 +v -0.6935 0.1951 0.6935 +v -0.9061 0.1951 0.3753 +v -0.9808 0.1951 0.0000 +v -0.9061 0.1951 -0.3753 +v -0.6935 0.1951 -0.6935 +v -0.3753 0.1951 -0.9061 +v -0.0000 0.1951 -0.9808 +v 0.3753 0.1951 -0.9061 +v 0.6935 0.1951 -0.6935 +v 0.9061 0.1951 -0.3753 +v 0.9808 0.1951 -0.0000 +v 1.0000 0.0000 0.0000 +v 0.9239 0.0000 0.3827 +v 0.7071 0.0000 0.7071 +v 0.3827 0.0000 0.9239 +v 0.0000 0.0000 1.0000 +v -0.3827 0.0000 0.9239 +v -0.7071 0.0000 0.7071 +v -0.9239 0.0000 0.3827 +v -1.0000 0.0000 0.0000 +v -0.9239 0.0000 -0.3827 +v -0.7071 0.0000 -0.7071 +v -0.3827 0.0000 -0.9239 +v -0.0000 0.0000 -1.0000 +v 0.3827 0.0000 -0.9239 +v 0.7071 0.0000 -0.7071 +v 0.9239 0.0000 -0.3827 +v 1.0000 0.0000 -0.0000 +v 0.9808 -0.1951 0.0000 +v 0.9061 -0.1951 0.3753 +v 0.6935 -0.1951 0.6935 +v 0.3753 -0.1951 0.9061 +v 0.0000 -0.1951 0.9808 +v -0.3753 -0.1951 0.9061 +v -0.6935 -0.1951 0.6935 +v -0.9061 -0.1951 0.3753 +v -0.9808 -0.1951 0.0000 +v -0.9061 -0.1951 -0.3753 +v -0.6935 -0.1951 -0.6935 +v -0.3753 -0.1951 -0.9061 +v -0.0000 -0.1951 -0.9808 +v 0.3753 -0.1951 -0.9061 +v 0.6935 -0.1951 -0.6935 +v 0.9061 -0.1951 -0.3753 +v 0.9808 -0.1951 -0.0000 +v 0.9239 -0.3827 0.0000 +v 0.8536 -0.3827 0.3536 +v 0.6533 -0.3827 0.6533 +v 0.3536 -0.3827 0.8536 +v 0.0000 -0.3827 0.9239 +v -0.3536 -0.3827 0.8536 +v -0.6533 -0.3827 0.6533 +v -0.8536 -0.3827 0.3536 +v -0.9239 -0.3827 0.0000 +v -0.8536 -0.3827 -0.3536 +v -0.6533 -0.3827 -0.6533 +v -0.3536 -0.3827 -0.8536 +v -0.0000 -0.3827 -0.9239 +v 0.3536 -0.3827 -0.8536 +v 0.6533 -0.3827 -0.6533 +v 0.8536 -0.3827 -0.3536 +v 0.9239 -0.3827 -0.0000 +v 0.8315 -0.5556 0.0000 +v 0.7682 -0.5556 0.3182 +v 0.5879 -0.5556 0.5879 +v 0.3182 -0.5556 0.7682 +v 0.0000 -0.5556 0.8315 +v -0.3182 -0.5556 0.7682 +v -0.5879 -0.5556 0.5879 +v -0.7682 -0.5556 0.3182 +v -0.8315 -0.5556 0.0000 +v -0.7682 -0.5556 -0.3182 +v -0.5879 -0.5556 -0.5879 +v -0.3182 -0.5556 -0.7682 +v -0.0000 -0.5556 -0.8315 +v 0.3182 -0.5556 -0.7682 +v 0.5879 -0.5556 -0.5879 +v 0.7682 -0.5556 -0.3182 +v 0.8315 -0.5556 -0.0000 +v 0.7071 -0.7071 0.0000 +v 0.6533 -0.7071 0.2706 +v 0.5000 -0.7071 0.5000 +v 0.2706 -0.7071 0.6533 +v 0.0000 -0.7071 0.7071 +v -0.2706 -0.7071 0.6533 +v -0.5000 -0.7071 0.5000 +v -0.6533 -0.7071 0.2706 +v -0.7071 -0.7071 0.0000 +v -0.6533 -0.7071 -0.2706 +v -0.5000 -0.7071 -0.5000 +v -0.2706 -0.7071 -0.6533 +v -0.0000 -0.7071 -0.7071 +v 0.2706 -0.7071 -0.6533 +v 0.5000 -0.7071 -0.5000 +v 0.6533 -0.7071 -0.2706 +v 0.7071 -0.7071 -0.0000 +v 0.5556 -0.8315 0.0000 +v 0.5133 -0.8315 0.2126 +v 0.3928 -0.8315 0.3928 +v 0.2126 -0.8315 0.5133 +v 0.0000 -0.8315 0.5556 +v -0.2126 -0.8315 0.5133 +v -0.3928 -0.8315 0.3928 +v -0.5133 -0.8315 0.2126 +v -0.5556 -0.8315 0.0000 +v -0.5133 -0.8315 -0.2126 +v -0.3928 -0.8315 -0.3928 +v -0.2126 -0.8315 -0.5133 +v -0.0000 -0.8315 -0.5556 +v 0.2126 -0.8315 -0.5133 +v 0.3928 -0.8315 -0.3928 +v 0.5133 -0.8315 -0.2126 +v 0.5556 -0.8315 -0.0000 +v 0.3827 -0.9239 0.0000 +v 0.3536 -0.9239 0.1464 +v 0.2706 -0.9239 0.2706 +v 0.1464 -0.9239 0.3536 +v 0.0000 -0.9239 0.3827 +v -0.1464 -0.9239 0.3536 +v -0.2706 -0.9239 0.2706 +v -0.3536 -0.9239 0.1464 +v -0.3827 -0.9239 0.0000 +v -0.3536 -0.9239 -0.1464 +v -0.2706 -0.9239 -0.2706 +v -0.1464 -0.9239 -0.3536 +v -0.0000 -0.9239 -0.3827 +v 0.1464 -0.9239 -0.3536 +v 0.2706 -0.9239 -0.2706 +v 0.3536 -0.9239 -0.1464 +v 0.3827 -0.9239 -0.0000 +v 0.1951 -0.9808 0.0000 +v 0.1802 -0.9808 0.0747 +v 0.1379 -0.9808 0.1379 +v 0.0747 -0.9808 0.1802 +v 0.0000 -0.9808 0.1951 +v -0.0747 -0.9808 0.1802 +v -0.1379 -0.9808 0.1379 +v -0.1802 -0.9808 0.0747 +v -0.1951 -0.9808 0.0000 +v -0.1802 -0.9808 -0.0747 +v -0.1379 -0.9808 -0.1379 +v -0.0747 -0.9808 -0.1802 +v -0.0000 -0.9808 -0.1951 +v 0.0747 -0.9808 -0.1802 +v 0.1379 -0.9808 -0.1379 +v 0.1802 -0.9808 -0.0747 +v 0.1951 -0.9808 -0.0000 +v 0.0000 -1.0000 0.0000 +v 0.0000 -1.0000 0.0000 +v 0.0000 -1.0000 0.0000 +v 0.0000 -1.0000 0.0000 +v 0.0000 -1.0000 0.0000 +v -0.0000 -1.0000 0.0000 +v -0.0000 -1.0000 0.0000 +v -0.0000 -1.0000 0.0000 +v -0.0000 -1.0000 0.0000 +v -0.0000 -1.0000 -0.0000 +v -0.0000 -1.0000 -0.0000 +v -0.0000 -1.0000 -0.0000 +v -0.0000 -1.0000 -0.0000 +v 0.0000 -1.0000 -0.0000 +v 0.0000 -1.0000 -0.0000 +v 0.0000 -1.0000 -0.0000 +v 0.0000 -1.0000 -0.0000 +vt 0.0000 1.0000 +vt 0.0625 1.0000 +vt 0.1250 1.0000 +vt 0.1875 1.0000 +vt 0.2500 1.0000 +vt 0.3125 1.0000 +vt 0.3750 1.0000 +vt 0.4375 1.0000 +vt 0.5000 1.0000 +vt 0.5625 1.0000 +vt 0.6250 1.0000 +vt 0.6875 1.0000 +vt 0.7500 1.0000 +vt 0.8125 1.0000 +vt 0.8750 1.0000 +vt 0.9375 1.0000 +vt 1.0000 1.0000 +vt 0.0000 0.9375 +vt 0.0625 0.9375 +vt 0.1250 0.9375 +vt 0.1875 0.9375 +vt 0.2500 0.9375 +vt 0.3125 0.9375 +vt 0.3750 0.9375 +vt 0.4375 0.9375 +vt 0.5000 0.9375 +vt 0.5625 0.9375 +vt 0.6250 0.9375 +vt 0.6875 0.9375 +vt 0.7500 0.9375 +vt 0.8125 0.9375 +vt 0.8750 0.9375 +vt 0.9375 0.9375 +vt 1.0000 0.9375 +vt 0.0000 0.8750 +vt 0.0625 0.8750 +vt 0.1250 0.8750 +vt 0.1875 0.8750 +vt 0.2500 0.8750 +vt 0.3125 0.8750 +vt 0.3750 0.8750 +vt 0.4375 0.8750 +vt 0.5000 0.8750 +vt 0.5625 0.8750 +vt 0.6250 0.8750 +vt 0.6875 0.8750 +vt 0.7500 0.8750 +vt 0.8125 0.8750 +vt 0.8750 0.8750 +vt 0.9375 0.8750 +vt 1.0000 0.8750 +vt 0.0000 0.8125 +vt 0.0625 0.8125 +vt 0.1250 0.8125 +vt 0.1875 0.8125 +vt 0.2500 0.8125 +vt 0.3125 0.8125 +vt 0.3750 0.8125 +vt 0.4375 0.8125 +vt 0.5000 0.8125 +vt 0.5625 0.8125 +vt 0.6250 0.8125 +vt 0.6875 0.8125 +vt 0.7500 0.8125 +vt 0.8125 0.8125 +vt 0.8750 0.8125 +vt 0.9375 0.8125 +vt 1.0000 0.8125 +vt 0.0000 0.7500 +vt 0.0625 0.7500 +vt 0.1250 0.7500 +vt 0.1875 0.7500 +vt 0.2500 0.7500 +vt 0.3125 0.7500 +vt 0.3750 0.7500 +vt 0.4375 0.7500 +vt 0.5000 0.7500 +vt 0.5625 0.7500 +vt 0.6250 0.7500 +vt 0.6875 0.7500 +vt 0.7500 0.7500 +vt 0.8125 0.7500 +vt 0.8750 0.7500 +vt 0.9375 0.7500 +vt 1.0000 0.7500 +vt 0.0000 0.6875 +vt 0.0625 0.6875 +vt 0.1250 0.6875 +vt 0.1875 0.6875 +vt 0.2500 0.6875 +vt 0.3125 0.6875 +vt 0.3750 0.6875 +vt 0.4375 0.6875 +vt 0.5000 0.6875 +vt 0.5625 0.6875 +vt 0.6250 0.6875 +vt 0.6875 0.6875 +vt 0.7500 0.6875 +vt 0.8125 0.6875 +vt 0.8750 0.6875 +vt 0.9375 0.6875 +vt 1.0000 0.6875 +vt 0.0000 0.6250 +vt 0.0625 0.6250 +vt 0.1250 0.6250 +vt 0.1875 0.6250 +vt 0.2500 0.6250 +vt 0.3125 0.6250 +vt 0.3750 0.6250 +vt 0.4375 0.6250 +vt 0.5000 0.6250 +vt 0.5625 0.6250 +vt 0.6250 0.6250 +vt 0.6875 0.6250 +vt 0.7500 0.6250 +vt 0.8125 0.6250 +vt 0.8750 0.6250 +vt 0.9375 0.6250 +vt 1.0000 0.6250 +vt 0.0000 0.5625 +vt 0.0625 0.5625 +vt 0.1250 0.5625 +vt 0.1875 0.5625 +vt 0.2500 0.5625 +vt 0.3125 0.5625 +vt 0.3750 0.5625 +vt 0.4375 0.5625 +vt 0.5000 0.5625 +vt 0.5625 0.5625 +vt 0.6250 0.5625 +vt 0.6875 0.5625 +vt 0.7500 0.5625 +vt 0.8125 0.5625 +vt 0.8750 0.5625 +vt 0.9375 0.5625 +vt 1.0000 0.5625 +vt 0.0000 0.5000 +vt 0.0625 0.5000 +vt 0.1250 0.5000 +vt 0.1875 0.5000 +vt 0.2500 0.5000 +vt 0.3125 0.5000 +vt 0.3750 0.5000 +vt 0.4375 0.5000 +vt 0.5000 0.5000 +vt 0.5625 0.5000 +vt 0.6250 0.5000 +vt 0.6875 0.5000 +vt 0.7500 0.5000 +vt 0.8125 0.5000 +vt 0.8750 0.5000 +vt 0.9375 0.5000 +vt 1.0000 0.5000 +vt 0.0000 0.4375 +vt 0.0625 0.4375 +vt 0.1250 0.4375 +vt 0.1875 0.4375 +vt 0.2500 0.4375 +vt 0.3125 0.4375 +vt 0.3750 0.4375 +vt 0.4375 0.4375 +vt 0.5000 0.4375 +vt 0.5625 0.4375 +vt 0.6250 0.4375 +vt 0.6875 0.4375 +vt 0.7500 0.4375 +vt 0.8125 0.4375 +vt 0.8750 0.4375 +vt 0.9375 0.4375 +vt 1.0000 0.4375 +vt 0.0000 0.3750 +vt 0.0625 0.3750 +vt 0.1250 0.3750 +vt 0.1875 0.3750 +vt 0.2500 0.3750 +vt 0.3125 0.3750 +vt 0.3750 0.3750 +vt 0.4375 0.3750 +vt 0.5000 0.3750 +vt 0.5625 0.3750 +vt 0.6250 0.3750 +vt 0.6875 0.3750 +vt 0.7500 0.3750 +vt 0.8125 0.3750 +vt 0.8750 0.3750 +vt 0.9375 0.3750 +vt 1.0000 0.3750 +vt 0.0000 0.3125 +vt 0.0625 0.3125 +vt 0.1250 0.3125 +vt 0.1875 0.3125 +vt 0.2500 0.3125 +vt 0.3125 0.3125 +vt 0.3750 0.3125 +vt 0.4375 0.3125 +vt 0.5000 0.3125 +vt 0.5625 0.3125 +vt 0.6250 0.3125 +vt 0.6875 0.3125 +vt 0.7500 0.3125 +vt 0.8125 0.3125 +vt 0.8750 0.3125 +vt 0.9375 0.3125 +vt 1.0000 0.3125 +vt 0.0000 0.2500 +vt 0.0625 0.2500 +vt 0.1250 0.2500 +vt 0.1875 0.2500 +vt 0.2500 0.2500 +vt 0.3125 0.2500 +vt 0.3750 0.2500 +vt 0.4375 0.2500 +vt 0.5000 0.2500 +vt 0.5625 0.2500 +vt 0.6250 0.2500 +vt 0.6875 0.2500 +vt 0.7500 0.2500 +vt 0.8125 0.2500 +vt 0.8750 0.2500 +vt 0.9375 0.2500 +vt 1.0000 0.2500 +vt 0.0000 0.1875 +vt 0.0625 0.1875 +vt 0.1250 0.1875 +vt 0.1875 0.1875 +vt 0.2500 0.1875 +vt 0.3125 0.1875 +vt 0.3750 0.1875 +vt 0.4375 0.1875 +vt 0.5000 0.1875 +vt 0.5625 0.1875 +vt 0.6250 0.1875 +vt 0.6875 0.1875 +vt 0.7500 0.1875 +vt 0.8125 0.1875 +vt 0.8750 0.1875 +vt 0.9375 0.1875 +vt 1.0000 0.1875 +vt 0.0000 0.1250 +vt 0.0625 0.1250 +vt 0.1250 0.1250 +vt 0.1875 0.1250 +vt 0.2500 0.1250 +vt 0.3125 0.1250 +vt 0.3750 0.1250 +vt 0.4375 0.1250 +vt 0.5000 0.1250 +vt 0.5625 0.1250 +vt 0.6250 0.1250 +vt 0.6875 0.1250 +vt 0.7500 0.1250 +vt 0.8125 0.1250 +vt 0.8750 0.1250 +vt 0.9375 0.1250 +vt 1.0000 0.1250 +vt 0.0000 0.0625 +vt 0.0625 0.0625 +vt 0.1250 0.0625 +vt 0.1875 0.0625 +vt 0.2500 0.0625 +vt 0.3125 0.0625 +vt 0.3750 0.0625 +vt 0.4375 0.0625 +vt 0.5000 0.0625 +vt 0.5625 0.0625 +vt 0.6250 0.0625 +vt 0.6875 0.0625 +vt 0.7500 0.0625 +vt 0.8125 0.0625 +vt 0.8750 0.0625 +vt 0.9375 0.0625 +vt 1.0000 0.0625 +vt 0.0000 0.0000 +vt 0.0625 0.0000 +vt 0.1250 0.0000 +vt 0.1875 0.0000 +vt 0.2500 0.0000 +vt 0.3125 0.0000 +vt 0.3750 0.0000 +vt 0.4375 0.0000 +vt 0.5000 0.0000 +vt 0.5625 0.0000 +vt 0.6250 0.0000 +vt 0.6875 0.0000 +vt 0.7500 0.0000 +vt 0.8125 0.0000 +vt 0.8750 0.0000 +vt 0.9375 0.0000 +vt 1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.1951 0.9808 0.0000 +vn 0.1802 0.9808 0.0747 +vn 0.1379 0.9808 0.1379 +vn 0.0747 0.9808 0.1802 +vn 0.0000 0.9808 0.1951 +vn -0.0747 0.9808 0.1802 +vn -0.1379 0.9808 0.1379 +vn -0.1802 0.9808 0.0747 +vn -0.1951 0.9808 0.0000 +vn -0.1802 0.9808 -0.0747 +vn -0.1379 0.9808 -0.1379 +vn -0.0747 0.9808 -0.1802 +vn -0.0000 0.9808 -0.1951 +vn 0.0747 0.9808 -0.1802 +vn 0.1379 0.9808 -0.1379 +vn 0.1802 0.9808 -0.0747 +vn 0.1951 0.9808 -0.0000 +vn 0.3827 0.9239 0.0000 +vn 0.3536 0.9239 0.1464 +vn 0.2706 0.9239 0.2706 +vn 0.1464 0.9239 0.3536 +vn 0.0000 0.9239 0.3827 +vn -0.1464 0.9239 0.3536 +vn -0.2706 0.9239 0.2706 +vn -0.3536 0.9239 0.1464 +vn -0.3827 0.9239 0.0000 +vn -0.3536 0.9239 -0.1464 +vn -0.2706 0.9239 -0.2706 +vn -0.1464 0.9239 -0.3536 +vn -0.0000 0.9239 -0.3827 +vn 0.1464 0.9239 -0.3536 +vn 0.2706 0.9239 -0.2706 +vn 0.3536 0.9239 -0.1464 +vn 0.3827 0.9239 -0.0000 +vn 0.5556 0.8315 0.0000 +vn 0.5133 0.8315 0.2126 +vn 0.3928 0.8315 0.3928 +vn 0.2126 0.8315 0.5133 +vn 0.0000 0.8315 0.5556 +vn -0.2126 0.8315 0.5133 +vn -0.3928 0.8315 0.3928 +vn -0.5133 0.8315 0.2126 +vn -0.5556 0.8315 0.0000 +vn -0.5133 0.8315 -0.2126 +vn -0.3928 0.8315 -0.3928 +vn -0.2126 0.8315 -0.5133 +vn -0.0000 0.8315 -0.5556 +vn 0.2126 0.8315 -0.5133 +vn 0.3928 0.8315 -0.3928 +vn 0.5133 0.8315 -0.2126 +vn 0.5556 0.8315 -0.0000 +vn 0.7071 0.7071 0.0000 +vn 0.6533 0.7071 0.2706 +vn 0.5000 0.7071 0.5000 +vn 0.2706 0.7071 0.6533 +vn 0.0000 0.7071 0.7071 +vn -0.2706 0.7071 0.6533 +vn -0.5000 0.7071 0.5000 +vn -0.6533 0.7071 0.2706 +vn -0.7071 0.7071 0.0000 +vn -0.6533 0.7071 -0.2706 +vn -0.5000 0.7071 -0.5000 +vn -0.2706 0.7071 -0.6533 +vn -0.0000 0.7071 -0.7071 +vn 0.2706 0.7071 -0.6533 +vn 0.5000 0.7071 -0.5000 +vn 0.6533 0.7071 -0.2706 +vn 0.7071 0.7071 -0.0000 +vn 0.8315 0.5556 0.0000 +vn 0.7682 0.5556 0.3182 +vn 0.5879 0.5556 0.5879 +vn 0.3182 0.5556 0.7682 +vn 0.0000 0.5556 0.8315 +vn -0.3182 0.5556 0.7682 +vn -0.5879 0.5556 0.5879 +vn -0.7682 0.5556 0.3182 +vn -0.8315 0.5556 0.0000 +vn -0.7682 0.5556 -0.3182 +vn -0.5879 0.5556 -0.5879 +vn -0.3182 0.5556 -0.7682 +vn -0.0000 0.5556 -0.8315 +vn 0.3182 0.5556 -0.7682 +vn 0.5879 0.5556 -0.5879 +vn 0.7682 0.5556 -0.3182 +vn 0.8315 0.5556 -0.0000 +vn 0.9239 0.3827 0.0000 +vn 0.8536 0.3827 0.3536 +vn 0.6533 0.3827 0.6533 +vn 0.3536 0.3827 0.8536 +vn 0.0000 0.3827 0.9239 +vn -0.3536 0.3827 0.8536 +vn -0.6533 0.3827 0.6533 +vn -0.8536 0.3827 0.3536 +vn -0.9239 0.3827 0.0000 +vn -0.8536 0.3827 -0.3536 +vn -0.6533 0.3827 -0.6533 +vn -0.3536 0.3827 -0.8536 +vn -0.0000 0.3827 -0.9239 +vn 0.3536 0.3827 -0.8536 +vn 0.6533 0.3827 -0.6533 +vn 0.8536 0.3827 -0.3536 +vn 0.9239 0.3827 -0.0000 +vn 0.9808 0.1951 0.0000 +vn 0.9061 0.1951 0.3753 +vn 0.6935 0.1951 0.6935 +vn 0.3753 0.1951 0.9061 +vn 0.0000 0.1951 0.9808 +vn -0.3753 0.1951 0.9061 +vn -0.6935 0.1951 0.6935 +vn -0.9061 0.1951 0.3753 +vn -0.9808 0.1951 0.0000 +vn -0.9061 0.1951 -0.3753 +vn -0.6935 0.1951 -0.6935 +vn -0.3753 0.1951 -0.9061 +vn -0.0000 0.1951 -0.9808 +vn 0.3753 0.1951 -0.9061 +vn 0.6935 0.1951 -0.6935 +vn 0.9061 0.1951 -0.3753 +vn 0.9808 0.1951 -0.0000 +vn 1.0000 0.0000 0.0000 +vn 0.9239 0.0000 0.3827 +vn 0.7071 0.0000 0.7071 +vn 0.3827 0.0000 0.9239 +vn 0.0000 0.0000 1.0000 +vn -0.3827 0.0000 0.9239 +vn -0.7071 0.0000 0.7071 +vn -0.9239 0.0000 0.3827 +vn -1.0000 0.0000 0.0000 +vn -0.9239 0.0000 -0.3827 +vn -0.7071 0.0000 -0.7071 +vn -0.3827 0.0000 -0.9239 +vn -0.0000 0.0000 -1.0000 +vn 0.3827 0.0000 -0.9239 +vn 0.7071 0.0000 -0.7071 +vn 0.9239 0.0000 -0.3827 +vn 1.0000 0.0000 -0.0000 +vn 0.9808 -0.1951 0.0000 +vn 0.9061 -0.1951 0.3753 +vn 0.6935 -0.1951 0.6935 +vn 0.3753 -0.1951 0.9061 +vn 0.0000 -0.1951 0.9808 +vn -0.3753 -0.1951 0.9061 +vn -0.6935 -0.1951 0.6935 +vn -0.9061 -0.1951 0.3753 +vn -0.9808 -0.1951 0.0000 +vn -0.9061 -0.1951 -0.3753 +vn -0.6935 -0.1951 -0.6935 +vn -0.3753 -0.1951 -0.9061 +vn -0.0000 -0.1951 -0.9808 +vn 0.3753 -0.1951 -0.9061 +vn 0.6935 -0.1951 -0.6935 +vn 0.9061 -0.1951 -0.3753 +vn 0.9808 -0.1951 -0.0000 +vn 0.9239 -0.3827 0.0000 +vn 0.8536 -0.3827 0.3536 +vn 0.6533 -0.3827 0.6533 +vn 0.3536 -0.3827 0.8536 +vn 0.0000 -0.3827 0.9239 +vn -0.3536 -0.3827 0.8536 +vn -0.6533 -0.3827 0.6533 +vn -0.8536 -0.3827 0.3536 +vn -0.9239 -0.3827 0.0000 +vn -0.8536 -0.3827 -0.3536 +vn -0.6533 -0.3827 -0.6533 +vn -0.3536 -0.3827 -0.8536 +vn -0.0000 -0.3827 -0.9239 +vn 0.3536 -0.3827 -0.8536 +vn 0.6533 -0.3827 -0.6533 +vn 0.8536 -0.3827 -0.3536 +vn 0.9239 -0.3827 -0.0000 +vn 0.8315 -0.5556 0.0000 +vn 0.7682 -0.5556 0.3182 +vn 0.5879 -0.5556 0.5879 +vn 0.3182 -0.5556 0.7682 +vn 0.0000 -0.5556 0.8315 +vn -0.3182 -0.5556 0.7682 +vn -0.5879 -0.5556 0.5879 +vn -0.7682 -0.5556 0.3182 +vn -0.8315 -0.5556 0.0000 +vn -0.7682 -0.5556 -0.3182 +vn -0.5879 -0.5556 -0.5879 +vn -0.3182 -0.5556 -0.7682 +vn -0.0000 -0.5556 -0.8315 +vn 0.3182 -0.5556 -0.7682 +vn 0.5879 -0.5556 -0.5879 +vn 0.7682 -0.5556 -0.3182 +vn 0.8315 -0.5556 -0.0000 +vn 0.7071 -0.7071 0.0000 +vn 0.6533 -0.7071 0.2706 +vn 0.5000 -0.7071 0.5000 +vn 0.2706 -0.7071 0.6533 +vn 0.0000 -0.7071 0.7071 +vn -0.2706 -0.7071 0.6533 +vn -0.5000 -0.7071 0.5000 +vn -0.6533 -0.7071 0.2706 +vn -0.7071 -0.7071 0.0000 +vn -0.6533 -0.7071 -0.2706 +vn -0.5000 -0.7071 -0.5000 +vn -0.2706 -0.7071 -0.6533 +vn -0.0000 -0.7071 -0.7071 +vn 0.2706 -0.7071 -0.6533 +vn 0.5000 -0.7071 -0.5000 +vn 0.6533 -0.7071 -0.2706 +vn 0.7071 -0.7071 -0.0000 +vn 0.5556 -0.8315 0.0000 +vn 0.5133 -0.8315 0.2126 +vn 0.3928 -0.8315 0.3928 +vn 0.2126 -0.8315 0.5133 +vn 0.0000 -0.8315 0.5556 +vn -0.2126 -0.8315 0.5133 +vn -0.3928 -0.8315 0.3928 +vn -0.5133 -0.8315 0.2126 +vn -0.5556 -0.8315 0.0000 +vn -0.5133 -0.8315 -0.2126 +vn -0.3928 -0.8315 -0.3928 +vn -0.2126 -0.8315 -0.5133 +vn -0.0000 -0.8315 -0.5556 +vn 0.2126 -0.8315 -0.5133 +vn 0.3928 -0.8315 -0.3928 +vn 0.5133 -0.8315 -0.2126 +vn 0.5556 -0.8315 -0.0000 +vn 0.3827 -0.9239 0.0000 +vn 0.3536 -0.9239 0.1464 +vn 0.2706 -0.9239 0.2706 +vn 0.1464 -0.9239 0.3536 +vn 0.0000 -0.9239 0.3827 +vn -0.1464 -0.9239 0.3536 +vn -0.2706 -0.9239 0.2706 +vn -0.3536 -0.9239 0.1464 +vn -0.3827 -0.9239 0.0000 +vn -0.3536 -0.9239 -0.1464 +vn -0.2706 -0.9239 -0.2706 +vn -0.1464 -0.9239 -0.3536 +vn -0.0000 -0.9239 -0.3827 +vn 0.1464 -0.9239 -0.3536 +vn 0.2706 -0.9239 -0.2706 +vn 0.3536 -0.9239 -0.1464 +vn 0.3827 -0.9239 -0.0000 +vn 0.1951 -0.9808 0.0000 +vn 0.1802 -0.9808 0.0747 +vn 0.1379 -0.9808 0.1379 +vn 0.0747 -0.9808 0.1802 +vn 0.0000 -0.9808 0.1951 +vn -0.0747 -0.9808 0.1802 +vn -0.1379 -0.9808 0.1379 +vn -0.1802 -0.9808 0.0747 +vn -0.1951 -0.9808 0.0000 +vn -0.1802 -0.9808 -0.0747 +vn -0.1379 -0.9808 -0.1379 +vn -0.0747 -0.9808 -0.1802 +vn -0.0000 -0.9808 -0.1951 +vn 0.0747 -0.9808 -0.1802 +vn 0.1379 -0.9808 -0.1379 +vn 0.1802 -0.9808 -0.0747 +vn 0.1951 -0.9808 -0.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 -1.0000 0.0000 +vn -0.0000 -1.0000 0.0000 +vn -0.0000 -1.0000 0.0000 +vn -0.0000 -1.0000 0.0000 +vn -0.0000 -1.0000 0.0000 +vn -0.0000 -1.0000 -0.0000 +vn -0.0000 -1.0000 -0.0000 +vn -0.0000 -1.0000 -0.0000 +vn -0.0000 -1.0000 -0.0000 +vn 0.0000 -1.0000 -0.0000 +vn 0.0000 -1.0000 -0.0000 +vn 0.0000 -1.0000 -0.0000 +vn 0.0000 -1.0000 -0.0000 +usemtl m0 +f 1/1/1 18/18/18 19/19/19 +f 1/1/1 19/19/19 2/2/2 +f 2/2/2 19/19/19 20/20/20 +f 2/2/2 20/20/20 3/3/3 +f 3/3/3 20/20/20 21/21/21 +f 3/3/3 21/21/21 4/4/4 +f 4/4/4 21/21/21 22/22/22 +f 4/4/4 22/22/22 5/5/5 +f 5/5/5 22/22/22 23/23/23 +f 5/5/5 23/23/23 6/6/6 +f 6/6/6 23/23/23 24/24/24 +f 6/6/6 24/24/24 7/7/7 +f 7/7/7 24/24/24 25/25/25 +f 7/7/7 25/25/25 8/8/8 +f 8/8/8 25/25/25 26/26/26 +f 8/8/8 26/26/26 9/9/9 +f 9/9/9 26/26/26 27/27/27 +f 9/9/9 27/27/27 10/10/10 +f 10/10/10 27/27/27 28/28/28 +f 10/10/10 28/28/28 11/11/11 +f 11/11/11 28/28/28 29/29/29 +f 11/11/11 29/29/29 12/12/12 +f 12/12/12 29/29/29 30/30/30 +f 12/12/12 30/30/30 13/13/13 +f 13/13/13 30/30/30 31/31/31 +f 13/13/13 31/31/31 14/14/14 +f 14/14/14 31/31/31 32/32/32 +f 14/14/14 32/32/32 15/15/15 +f 15/15/15 32/32/32 33/33/33 +f 15/15/15 33/33/33 16/16/16 +f 16/16/16 33/33/33 34/34/34 +f 16/16/16 34/34/34 17/17/17 +f 18/18/18 35/35/35 36/36/36 +f 18/18/18 36/36/36 19/19/19 +f 19/19/19 36/36/36 37/37/37 +f 19/19/19 37/37/37 20/20/20 +f 20/20/20 37/37/37 38/38/38 +f 20/20/20 38/38/38 21/21/21 +f 21/21/21 38/38/38 39/39/39 +f 21/21/21 39/39/39 22/22/22 +f 22/22/22 39/39/39 40/40/40 +f 22/22/22 40/40/40 23/23/23 +f 23/23/23 40/40/40 41/41/41 +f 23/23/23 41/41/41 24/24/24 +f 24/24/24 41/41/41 42/42/42 +f 24/24/24 42/42/42 25/25/25 +f 25/25/25 42/42/42 43/43/43 +f 25/25/25 43/43/43 26/26/26 +f 26/26/26 43/43/43 44/44/44 +f 26/26/26 44/44/44 27/27/27 +f 27/27/27 44/44/44 45/45/45 +f 27/27/27 45/45/45 28/28/28 +f 28/28/28 45/45/45 46/46/46 +f 28/28/28 46/46/46 29/29/29 +f 29/29/29 46/46/46 47/47/47 +f 29/29/29 47/47/47 30/30/30 +f 30/30/30 47/47/47 48/48/48 +f 30/30/30 48/48/48 31/31/31 +f 31/31/31 48/48/48 49/49/49 +f 31/31/31 49/49/49 32/32/32 +f 32/32/32 49/49/49 50/50/50 +f 32/32/32 50/50/50 33/33/33 +f 33/33/33 50/50/50 51/51/51 +f 33/33/33 51/51/51 34/34/34 +f 35/35/35 52/52/52 53/53/53 +f 35/35/35 53/53/53 36/36/36 +f 36/36/36 53/53/53 54/54/54 +f 36/36/36 54/54/54 37/37/37 +f 37/37/37 54/54/54 55/55/55 +f 37/37/37 55/55/55 38/38/38 +f 38/38/38 55/55/55 56/56/56 +f 38/38/38 56/56/56 39/39/39 +f 39/39/39 56/56/56 57/57/57 +f 39/39/39 57/57/57 40/40/40 +f 40/40/40 57/57/57 58/58/58 +f 40/40/40 58/58/58 41/41/41 +f 41/41/41 58/58/58 59/59/59 +f 41/41/41 59/59/59 42/42/42 +f 42/42/42 59/59/59 60/60/60 +f 42/42/42 60/60/60 43/43/43 +f 43/43/43 60/60/60 61/61/61 +f 43/43/43 61/61/61 44/44/44 +f 44/44/44 61/61/61 62/62/62 +f 44/44/44 62/62/62 45/45/45 +f 45/45/45 62/62/62 63/63/63 +f 45/45/45 63/63/63 46/46/46 +f 46/46/46 63/63/63 64/64/64 +f 46/46/46 64/64/64 47/47/47 +f 47/47/47 64/64/64 65/65/65 +f 47/47/47 65/65/65 48/48/48 +f 48/48/48 65/65/65 66/66/66 +f 48/48/48 66/66/66 49/49/49 +f 49/49/49 66/66/66 67/67/67 +f 49/49/49 67/67/67 50/50/50 +f 50/50/50 67/67/67 68/68/68 +f 50/50/50 68/68/68 51/51/51 +f 52/52/52 69/69/69 70/70/70 +f 52/52/52 70/70/70 53/53/53 +f 53/53/53 70/70/70 71/71/71 +f 53/53/53 71/71/71 54/54/54 +f 54/54/54 71/71/71 72/72/72 +f 54/54/54 72/72/72 55/55/55 +f 55/55/55 72/72/72 73/73/73 +f 55/55/55 73/73/73 56/56/56 +f 56/56/56 73/73/73 74/74/74 +f 56/56/56 74/74/74 57/57/57 +f 57/57/57 74/74/74 75/75/75 +f 57/57/57 75/75/75 58/58/58 +f 58/58/58 75/75/75 76/76/76 +f 58/58/58 76/76/76 59/59/59 +f 59/59/59 76/76/76 77/77/77 +f 59/59/59 77/77/77 60/60/60 +f 60/60/60 77/77/77 78/78/78 +f 60/60/60 78/78/78 61/61/61 +f 61/61/61 78/78/78 79/79/79 +f 61/61/61 79/79/79 62/62/62 +f 62/62/62 79/79/79 80/80/80 +f 62/62/62 80/80/80 63/63/63 +f 63/63/63 80/80/80 81/81/81 +f 63/63/63 81/81/81 64/64/64 +f 64/64/64 81/81/81 82/82/82 +f 64/64/64 82/82/82 65/65/65 +f 65/65/65 82/82/82 83/83/83 +f 65/65/65 83/83/83 66/66/66 +f 66/66/66 83/83/83 84/84/84 +f 66/66/66 84/84/84 67/67/67 +f 67/67/67 84/84/84 85/85/85 +f 67/67/67 85/85/85 68/68/68 +f 69/69/69 86/86/86 87/87/87 +f 69/69/69 87/87/87 70/70/70 +f 70/70/70 87/87/87 88/88/88 +f 70/70/70 88/88/88 71/71/71 +f 71/71/71 88/88/88 89/89/89 +f 71/71/71 89/89/89 72/72/72 +f 72/72/72 89/89/89 90/90/90 +f 72/72/72 90/90/90 73/73/73 +f 73/73/73 90/90/90 91/91/91 +f 73/73/73 91/91/91 74/74/74 +f 74/74/74 91/91/91 92/92/92 +f 74/74/74 92/92/92 75/75/75 +f 75/75/75 92/92/92 93/93/93 +f 75/75/75 93/93/93 76/76/76 +f 76/76/76 93/93/93 94/94/94 +f 76/76/76 94/94/94 77/77/77 +f 77/77/77 94/94/94 95/95/95 +f 77/77/77 95/95/95 78/78/78 +f 78/78/78 95/95/95 96/96/96 +f 78/78/78 96/96/96 79/79/79 +f 79/79/79 96/96/96 97/97/97 +f 79/79/79 97/97/97 80/80/80 +f 80/80/80 97/97/97 98/98/98 +f 80/80/80 98/98/98 81/81/81 +f 81/81/81 98/98/98 99/99/99 +f 81/81/81 99/99/99 82/82/82 +f 82/82/82 99/99/99 100/100/100 +f 82/82/82 100/100/100 83/83/83 +f 83/83/83 100/100/100 101/101/101 +f 83/83/83 101/101/101 84/84/84 +f 84/84/84 101/101/101 102/102/102 +f 84/84/84 102/102/102 85/85/85 +f 86/86/86 103/103/103 104/104/104 +f 86/86/86 104/104/104 87/87/87 +f 87/87/87 104/104/104 105/105/105 +f 87/87/87 105/105/105 88/88/88 +f 88/88/88 105/105/105 106/106/106 +f 88/88/88 106/106/106 89/89/89 +f 89/89/89 106/106/106 107/107/107 +f 89/89/89 107/107/107 90/90/90 +f 90/90/90 107/107/107 108/108/108 +f 90/90/90 108/108/108 91/91/91 +f 91/91/91 108/108/108 109/109/109 +f 91/91/91 109/109/109 92/92/92 +f 92/92/92 109/109/109 110/110/110 +f 92/92/92 110/110/110 93/93/93 +f 93/93/93 110/110/110 111/111/111 +f 93/93/93 111/111/111 94/94/94 +f 94/94/94 111/111/111 112/112/112 +f 94/94/94 112/112/112 95/95/95 +f 95/95/95 112/112/112 113/113/113 +f 95/95/95 113/113/113 96/96/96 +f 96/96/96 113/113/113 114/114/114 +f 96/96/96 114/114/114 97/97/97 +f 97/97/97 114/114/114 115/115/115 +f 97/97/97 115/115/115 98/98/98 +f 98/98/98 115/115/115 116/116/116 +f 98/98/98 116/116/116 99/99/99 +f 99/99/99 116/116/116 117/117/117 +f 99/99/99 117/117/117 100/100/100 +f 100/100/100 117/117/117 118/118/118 +f 100/100/100 118/118/118 101/101/101 +f 101/101/101 118/118/118 119/119/119 +f 101/101/101 119/119/119 102/102/102 +f 103/103/103 120/120/120 121/121/121 +f 103/103/103 121/121/121 104/104/104 +f 104/104/104 121/121/121 122/122/122 +f 104/104/104 122/122/122 105/105/105 +f 105/105/105 122/122/122 123/123/123 +f 105/105/105 123/123/123 106/106/106 +f 106/106/106 123/123/123 124/124/124 +f 106/106/106 124/124/124 107/107/107 +f 107/107/107 124/124/124 125/125/125 +f 107/107/107 125/125/125 108/108/108 +f 108/108/108 125/125/125 126/126/126 +f 108/108/108 126/126/126 109/109/109 +f 109/109/109 126/126/126 127/127/127 +f 109/109/109 127/127/127 110/110/110 +f 110/110/110 127/127/127 128/128/128 +f 110/110/110 128/128/128 111/111/111 +f 111/111/111 128/128/128 129/129/129 +f 111/111/111 129/129/129 112/112/112 +f 112/112/112 129/129/129 130/130/130 +f 112/112/112 130/130/130 113/113/113 +f 113/113/113 130/130/130 131/131/131 +f 113/113/113 131/131/131 114/114/114 +f 114/114/114 131/131/131 132/132/132 +f 114/114/114 132/132/132 115/115/115 +f 115/115/115 132/132/132 133/133/133 +f 115/115/115 133/133/133 116/116/116 +f 116/116/116 133/133/133 134/134/134 +f 116/116/116 134/134/134 117/117/117 +f 117/117/117 134/134/134 135/135/135 +f 117/117/117 135/135/135 118/118/118 +f 118/118/118 135/135/135 136/136/136 +f 118/118/118 136/136/136 119/119/119 +f 120/120/120 137/137/137 138/138/138 +f 120/120/120 138/138/138 121/121/121 +f 121/121/121 138/138/138 139/139/139 +f 121/121/121 139/139/139 122/122/122 +f 122/122/122 139/139/139 140/140/140 +f 122/122/122 140/140/140 123/123/123 +f 123/123/123 140/140/140 141/141/141 +f 123/123/123 141/141/141 124/124/124 +f 124/124/124 141/141/141 142/142/142 +f 124/124/124 142/142/142 125/125/125 +f 125/125/125 142/142/142 143/143/143 +f 125/125/125 143/143/143 126/126/126 +f 126/126/126 143/143/143 144/144/144 +f 126/126/126 144/144/144 127/127/127 +f 127/127/127 144/144/144 145/145/145 +f 127/127/127 145/145/145 128/128/128 +f 128/128/128 145/145/145 146/146/146 +f 128/128/128 146/146/146 129/129/129 +f 129/129/129 146/146/146 147/147/147 +f 129/129/129 147/147/147 130/130/130 +f 130/130/130 147/147/147 148/148/148 +f 130/130/130 148/148/148 131/131/131 +f 131/131/131 148/148/148 149/149/149 +f 131/131/131 149/149/149 132/132/132 +f 132/132/132 149/149/149 150/150/150 +f 132/132/132 150/150/150 133/133/133 +f 133/133/133 150/150/150 151/151/151 +f 133/133/133 151/151/151 134/134/134 +f 134/134/134 151/151/151 152/152/152 +f 134/134/134 152/152/152 135/135/135 +f 135/135/135 152/152/152 153/153/153 +f 135/135/135 153/153/153 136/136/136 +usemtl m1 +f 137/137/137 154/154/154 155/155/155 +f 137/137/137 155/155/155 138/138/138 +f 138/138/138 155/155/155 156/156/156 +f 138/138/138 156/156/156 139/139/139 +f 139/139/139 156/156/156 157/157/157 +f 139/139/139 157/157/157 140/140/140 +f 140/140/140 157/157/157 158/158/158 +f 140/140/140 158/158/158 141/141/141 +f 141/141/141 158/158/158 159/159/159 +f 141/141/141 159/159/159 142/142/142 +f 142/142/142 159/159/159 160/160/160 +f 142/142/142 160/160/160 143/143/143 +f 143/143/143 160/160/160 161/161/161 +f 143/143/143 161/161/161 144/144/144 +f 144/144/144 161/161/161 162/162/162 +f 144/144/144 162/162/162 145/145/145 +f 145/145/145 162/162/162 163/163/163 +f 145/145/145 163/163/163 146/146/146 +f 146/146/146 163/163/163 164/164/164 +f 146/146/146 164/164/164 147/147/147 +f 147/147/147 164/164/164 165/165/165 +f 147/147/147 165/165/165 148/148/148 +f 148/148/148 165/165/165 166/166/166 +f 148/148/148 166/166/166 149/149/149 +f 149/149/149 166/166/166 167/167/167 +f 149/149/149 167/167/167 150/150/150 +f 150/150/150 167/167/167 168/168/168 +f 150/150/150 168/168/168 151/151/151 +f 151/151/151 168/168/168 169/169/169 +f 151/151/151 169/169/169 152/152/152 +f 152/152/152 169/169/169 170/170/170 +f 152/152/152 170/170/170 153/153/153 +f 154/154/154 171/171/171 172/172/172 +f 154/154/154 172/172/172 155/155/155 +f 155/155/155 172/172/172 173/173/173 +f 155/155/155 173/173/173 156/156/156 +f 156/156/156 173/173/173 174/174/174 +f 156/156/156 174/174/174 157/157/157 +f 157/157/157 174/174/174 175/175/175 +f 157/157/157 175/175/175 158/158/158 +f 158/158/158 175/175/175 176/176/176 +f 158/158/158 176/176/176 159/159/159 +f 159/159/159 176/176/176 177/177/177 +f 159/159/159 177/177/177 160/160/160 +f 160/160/160 177/177/177 178/178/178 +f 160/160/160 178/178/178 161/161/161 +f 161/161/161 178/178/178 179/179/179 +f 161/161/161 179/179/179 162/162/162 +f 162/162/162 179/179/179 180/180/180 +f 162/162/162 180/180/180 163/163/163 +f 163/163/163 180/180/180 181/181/181 +f 163/163/163 181/181/181 164/164/164 +f 164/164/164 181/181/181 182/182/182 +f 164/164/164 182/182/182 165/165/165 +f 165/165/165 182/182/182 183/183/183 +f 165/165/165 183/183/183 166/166/166 +f 166/166/166 183/183/183 184/184/184 +f 166/166/166 184/184/184 167/167/167 +f 167/167/167 184/184/184 185/185/185 +f 167/167/167 185/185/185 168/168/168 +f 168/168/168 185/185/185 186/186/186 +f 168/168/168 186/186/186 169/169/169 +f 169/169/169 186/186/186 187/187/187 +f 169/169/169 187/187/187 170/170/170 +f 171/171/171 188/188/188 189/189/189 +f 171/171/171 189/189/189 172/172/172 +f 172/172/172 189/189/189 190/190/190 +f 172/172/172 190/190/190 173/173/173 +f 173/173/173 190/190/190 191/191/191 +f 173/173/173 191/191/191 174/174/174 +f 174/174/174 191/191/191 192/192/192 +f 174/174/174 192/192/192 175/175/175 +f 175/175/175 192/192/192 193/193/193 +f 175/175/175 193/193/193 176/176/176 +f 176/176/176 193/193/193 194/194/194 +f 176/176/176 194/194/194 177/177/177 +f 177/177/177 194/194/194 195/195/195 +f 177/177/177 195/195/195 178/178/178 +f 178/178/178 195/195/195 196/196/196 +f 178/178/178 196/196/196 179/179/179 +f 179/179/179 196/196/196 197/197/197 +f 179/179/179 197/197/197 180/180/180 +f 180/180/180 197/197/197 198/198/198 +f 180/180/180 198/198/198 181/181/181 +f 181/181/181 198/198/198 199/199/199 +f 181/181/181 199/199/199 182/182/182 +f 182/182/182 199/199/199 200/200/200 +f 182/182/182 200/200/200 183/183/183 +f 183/183/183 200/200/200 201/201/201 +f 183/183/183 201/201/201 184/184/184 +f 184/184/184 201/201/201 202/202/202 +f 184/184/184 202/202/202 185/185/185 +f 185/185/185 202/202/202 203/203/203 +f 185/185/185 203/203/203 186/186/186 +f 186/186/186 203/203/203 204/204/204 +f 186/186/186 204/204/204 187/187/187 +f 188/188/188 205/205/205 206/206/206 +f 188/188/188 206/206/206 189/189/189 +f 189/189/189 206/206/206 207/207/207 +f 189/189/189 207/207/207 190/190/190 +f 190/190/190 207/207/207 208/208/208 +f 190/190/190 208/208/208 191/191/191 +f 191/191/191 208/208/208 209/209/209 +f 191/191/191 209/209/209 192/192/192 +f 192/192/192 209/209/209 210/210/210 +f 192/192/192 210/210/210 193/193/193 +f 193/193/193 210/210/210 211/211/211 +f 193/193/193 211/211/211 194/194/194 +f 194/194/194 211/211/211 212/212/212 +f 194/194/194 212/212/212 195/195/195 +f 195/195/195 212/212/212 213/213/213 +f 195/195/195 213/213/213 196/196/196 +f 196/196/196 213/213/213 214/214/214 +f 196/196/196 214/214/214 197/197/197 +f 197/197/197 214/214/214 215/215/215 +f 197/197/197 215/215/215 198/198/198 +f 198/198/198 215/215/215 216/216/216 +f 198/198/198 216/216/216 199/199/199 +f 199/199/199 216/216/216 217/217/217 +f 199/199/199 217/217/217 200/200/200 +f 200/200/200 217/217/217 218/218/218 +f 200/200/200 218/218/218 201/201/201 +f 201/201/201 218/218/218 219/219/219 +f 201/201/201 219/219/219 202/202/202 +f 202/202/202 219/219/219 220/220/220 +f 202/202/202 220/220/220 203/203/203 +f 203/203/203 220/220/220 221/221/221 +f 203/203/203 221/221/221 204/204/204 +f 205/205/205 222/222/222 223/223/223 +f 205/205/205 223/223/223 206/206/206 +f 206/206/206 223/223/223 224/224/224 +f 206/206/206 224/224/224 207/207/207 +f 207/207/207 224/224/224 225/225/225 +f 207/207/207 225/225/225 208/208/208 +f 208/208/208 225/225/225 226/226/226 +f 208/208/208 226/226/226 209/209/209 +f 209/209/209 226/226/226 227/227/227 +f 209/209/209 227/227/227 210/210/210 +f 210/210/210 227/227/227 228/228/228 +f 210/210/210 228/228/228 211/211/211 +f 211/211/211 228/228/228 229/229/229 +f 211/211/211 229/229/229 212/212/212 +f 212/212/212 229/229/229 230/230/230 +f 212/212/212 230/230/230 213/213/213 +f 213/213/213 230/230/230 231/231/231 +f 213/213/213 231/231/231 214/214/214 +f 214/214/214 231/231/231 232/232/232 +f 214/214/214 232/232/232 215/215/215 +f 215/215/215 232/232/232 233/233/233 +f 215/215/215 233/233/233 216/216/216 +f 216/216/216 233/233/233 234/234/234 +f 216/216/216 234/234/234 217/217/217 +f 217/217/217 234/234/234 235/235/235 +f 217/217/217 235/235/235 218/218/218 +f 218/218/218 235/235/235 236/236/236 +f 218/218/218 236/236/236 219/219/219 +f 219/219/219 236/236/236 237/237/237 +f 219/219/219 237/237/237 220/220/220 +f 220/220/220 237/237/237 238/238/238 +f 220/220/220 238/238/238 221/221/221 +f 222/222/222 239/239/239 240/240/240 +f 222/222/222 240/240/240 223/223/223 +f 223/223/223 240/240/240 241/241/241 +f 223/223/223 241/241/241 224/224/224 +f 224/224/224 241/241/241 242/242/242 +f 224/224/224 242/242/242 225/225/225 +f 225/225/225 242/242/242 243/243/243 +f 225/225/225 243/243/243 226/226/226 +f 226/226/226 243/243/243 244/244/244 +f 226/226/226 244/244/244 227/227/227 +f 227/227/227 244/244/244 245/245/245 +f 227/227/227 245/245/245 228/228/228 +f 228/228/228 245/245/245 246/246/246 +f 228/228/228 246/246/246 229/229/229 +f 229/229/229 246/246/246 247/247/247 +f 229/229/229 247/247/247 230/230/230 +f 230/230/230 247/247/247 248/248/248 +f 230/230/230 248/248/248 231/231/231 +f 231/231/231 248/248/248 249/249/249 +f 231/231/231 249/249/249 232/232/232 +f 232/232/232 249/249/249 250/250/250 +f 232/232/232 250/250/250 233/233/233 +f 233/233/233 250/250/250 251/251/251 +f 233/233/233 251/251/251 234/234/234 +f 234/234/234 251/251/251 252/252/252 +f 234/234/234 252/252/252 235/235/235 +f 235/235/235 252/252/252 253/253/253 +f 235/235/235 253/253/253 236/236/236 +f 236/236/236 253/253/253 254/254/254 +f 236/236/236 254/254/254 237/237/237 +f 237/237/237 254/254/254 255/255/255 +f 237/237/237 255/255/255 238/238/238 +f 239/239/239 256/256/256 257/257/257 +f 239/239/239 257/257/257 240/240/240 +f 240/240/240 257/257/257 258/258/258 +f 240/240/240 258/258/258 241/241/241 +f 241/241/241 258/258/258 259/259/259 +f 241/241/241 259/259/259 242/242/242 +f 242/242/242 259/259/259 260/260/260 +f 242/242/242 260/260/260 243/243/243 +f 243/243/243 260/260/260 261/261/261 +f 243/243/243 261/261/261 244/244/244 +f 244/244/244 261/261/261 262/262/262 +f 244/244/244 262/262/262 245/245/245 +f 245/245/245 262/262/262 263/263/263 +f 245/245/245 263/263/263 246/246/246 +f 246/246/246 263/263/263 264/264/264 +f 246/246/246 264/264/264 247/247/247 +f 247/247/247 264/264/264 265/265/265 +f 247/247/247 265/265/265 248/248/248 +f 248/248/248 265/265/265 266/266/266 +f 248/248/248 266/266/266 249/249/249 +f 249/249/249 266/266/266 267/267/267 +f 249/249/249 267/267/267 250/250/250 +f 250/250/250 267/267/267 268/268/268 +f 250/250/250 268/268/268 251/251/251 +f 251/251/251 268/268/268 269/269/269 +f 251/251/251 269/269/269 252/252/252 +f 252/252/252 269/269/269 270/270/270 +f 252/252/252 270/270/270 253/253/253 +f 253/253/253 270/270/270 271/271/271 +f 253/253/253 271/271/271 254/254/254 +f 254/254/254 271/271/271 272/272/272 +f 254/254/254 272/272/272 255/255/255 +f 256/256/256 273/273/273 274/274/274 +f 256/256/256 274/274/274 257/257/257 +f 257/257/257 274/274/274 275/275/275 +f 257/257/257 275/275/275 258/258/258 +f 258/258/258 275/275/275 276/276/276 +f 258/258/258 276/276/276 259/259/259 +f 259/259/259 276/276/276 277/277/277 +f 259/259/259 277/277/277 260/260/260 +f 260/260/260 277/277/277 278/278/278 +f 260/260/260 278/278/278 261/261/261 +f 261/261/261 278/278/278 279/279/279 +f 261/261/261 279/279/279 262/262/262 +f 262/262/262 279/279/279 280/280/280 +f 262/262/262 280/280/280 263/263/263 +f 263/263/263 280/280/280 281/281/281 +f 263/263/263 281/281/281 264/264/264 +f 264/264/264 281/281/281 282/282/282 +f 264/264/264 282/282/282 265/265/265 +f 265/265/265 282/282/282 283/283/283 +f 265/265/265 283/283/283 266/266/266 +f 266/266/266 283/283/283 284/284/284 +f 266/266/266 284/284/284 267/267/267 +f 267/267/267 284/284/284 285/285/285 +f 267/267/267 285/285/285 268/268/268 +f 268/268/268 285/285/285 286/286/286 +f 268/268/268 286/286/286 269/269/269 +f 269/269/269 286/286/286 287/287/287 +f 269/269/269 287/287/287 270/270/270 +f 270/270/270 287/287/287 288/288/288 +f 270/270/270 288/288/288 271/271/271 +f 271/271/271 288/288/288 289/289/289 +f 271/271/271 289/289/289 272/272/272 diff --git a/test/unit/assets/normal_mapped.mtl b/test/unit/assets/normal_mapped.mtl new file mode 100644 index 0000000000..75391a4a70 --- /dev/null +++ b/test/unit/assets/normal_mapped.mtl @@ -0,0 +1,6 @@ +newmtl m0 +Kd 0.8 0.8 0.8 +map_Bump spheremap.jpg + +newmtl m1 +Kd 0.5 0.5 0.5 diff --git a/test/unit/assets/normal_mapped.obj b/test/unit/assets/normal_mapped.obj new file mode 100644 index 0000000000..971557ca7f --- /dev/null +++ b/test/unit/assets/normal_mapped.obj @@ -0,0 +1,17 @@ +mtllib normal_mapped.mtl +v 0 0 0 +v 1 0 0 +v 0 1 0 +v 1 1 0 +vt 0 0 +vt 1 0 +vt 0 1 +vt 1 1 +vn 0 0 1 +vn 0 0 1 +vn 0 0 1 +vn 0 0 1 +usemtl m0 +f 1/1/1 2/2/2 3/3/3 +usemtl m1 +f 2/2/2 4/4/4 3/3/3 diff --git a/test/unit/io/loadModel.js b/test/unit/io/loadModel.js index 0c2efa9cf7..f94bd68c15 100644 --- a/test/unit/io/loadModel.js +++ b/test/unit/io/loadModel.js @@ -116,6 +116,24 @@ suite('loadModel', function () { } }); + test('a normal-mapped OBJ carries the normal map on its part', async function () { + const fakeImage = { width: 1, height: 1 }; + mockP5Prototype.loadImage = async () => fakeImage; + try { + const model = await mockP5Prototype.loadModel( + '/test/unit/assets/normal_mapped.obj' + ); + // two materials, so two parts + assert.equal(model.parts.length, 2); + // the part with the normal map carries it + const normalMapped = model.parts.find(p => p.partState.normalTexture); + assert.ok(normalMapped, 'a part has the normal map'); + assert.equal(normalMapped.partState.normalTexture, fakeImage); + } finally { + delete mockP5Prototype.loadImage; + } + }); + test('a texture that fails to load is skipped without failing the model', async function () { mockP5Prototype.loadImage = async () => { throw new Error('Not Found'); diff --git a/test/unit/io/parseMtl.js b/test/unit/io/parseMtl.js index c01ef7947a..1a7dc30c17 100644 --- a/test/unit/io/parseMtl.js +++ b/test/unit/io/parseMtl.js @@ -13,6 +13,7 @@ suite('parseMtlData', function () { 'map_Kd diffuse.png', 'map_Ka ambient.png', 'map_Ks specular.png', + 'map_Ns shininess.png', 'map_Bump -bm 0.5 bump.png' ].join('\n'); @@ -28,6 +29,7 @@ suite('parseMtlData', function () { expect(m.texturePath).toEqual('diffuse.png'); expect(m.ambientTexturePath).toEqual('ambient.png'); expect(m.specularTexturePath).toEqual('specular.png'); + expect(m.shininessTexturePath).toEqual('shininess.png'); // bump options like -bm precede the path, so the path is the last token. expect(m.bumpTexturePath).toEqual('bump.png'); }); @@ -69,4 +71,44 @@ suite('mtlToPartState', function () { expect(state.fill).toBeNull(); expect(state.texture).toBeNull(); }); + + test('a specular map lands on the part state with a white base', function () { + const img = { width: 1, height: 1 }; + const state = mtlToPartState({ specularTexture: img }); + expect(state.specularTexture).toBe(img); + // with no explicit Ks, the base specular colour defaults to white so the + // map has something to modulate + expect(state.specularColor).toEqual([1, 1, 1]); + }); + + test('a specular map keeps an explicit Ks colour', function () { + const img = { width: 1, height: 1 }; + const state = mtlToPartState({ + specularColor: [0.5, 0.5, 0.5], + specularTexture: img + }); + expect(state.specularTexture).toBe(img); + expect(state.specularColor).toEqual([0.5, 0.5, 0.5]); + }); + + test('an ambient map lands on the part state with a white base', function () { + const img = { width: 1, height: 1 }; + const state = mtlToPartState({ ambientTexture: img }); + expect(state.ambientTexture).toBe(img); + expect(state.ambientColor).toEqual([1, 1, 1]); + }); + + test('a shininess map lands on the part state', function () { + const img = { width: 1, height: 1 }; + const state = mtlToPartState({ shininessTexture: img }); + expect(state.shininessTexture).toBe(img); + // the map scales a base shininess, which defaults to 1 + expect(state.shininess).toEqual(1); + }); + + test('a normal map lands on the part state', function () { + const img = { width: 1, height: 1 }; + const state = mtlToPartState({ normalTexture: img }); + expect(state.normalTexture).toBe(img); + }); }); diff --git a/test/unit/visual/cases/webgl.js b/test/unit/visual/cases/webgl.js index ed2af5fdc6..e43e59007e 100644 --- a/test/unit/visual/cases/webgl.js +++ b/test/unit/visual/cases/webgl.js @@ -411,6 +411,23 @@ visualSuite('WebGL', function () { screenshot(); } ); + visualTest( + 'a normal-mapped sphere shows surface detail under light', + async function (p5, screenshot) { + p5.createCanvas(50, 50, p5.WEBGL); + // the top half of bump_sphere.obj uses a normal map, the bottom is plain, + // so under a light the top shows surface detail and the bottom stays smooth + const model = await new Promise(resolve => + p5.loadModel('test/unit/assets/bump_sphere.obj', resolve) + ); + p5.background(255); + p5.pointLight(255, 255, 255, 100, -100, 200); + p5.noStroke(); + p5.scale(22); + p5.model(model); + screenshot(); + } + ); }); visualSuite('vertexProperty', function () { diff --git a/test/unit/visual/screenshots/WebGL/3DModel/a normal-mapped sphere shows surface detail under light/000.png b/test/unit/visual/screenshots/WebGL/3DModel/a normal-mapped sphere shows surface detail under light/000.png new file mode 100644 index 0000000000..a4d937fe35 Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/3DModel/a normal-mapped sphere shows surface detail under light/000.png differ diff --git a/test/unit/visual/screenshots/WebGL/3DModel/a normal-mapped sphere shows surface detail under light/metadata.json b/test/unit/visual/screenshots/WebGL/3DModel/a normal-mapped sphere shows surface detail under light/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/WebGL/3DModel/a normal-mapped sphere shows surface detail under light/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/webgl/p5.GeometryPart.js b/test/unit/webgl/p5.GeometryPart.js index 00cde74b0b..e13e0a1e8e 100644 --- a/test/unit/webgl/p5.GeometryPart.js +++ b/test/unit/webgl/p5.GeometryPart.js @@ -40,7 +40,11 @@ suite('p5.GeometryPart', function () { ambientColor: null, specularColor: null, shininess: null, - texture: null + texture: null, + specularTexture: null, + ambientTexture: null, + shininessTexture: null, + normalTexture: null }); });