|
@ -10,39 +10,51 @@ class LoardObj |
|
|
const OPEN = true; |
|
|
const OPEN = true; |
|
|
const CLOSE = false; |
|
|
const CLOSE = false; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* @var string $objFile obj文件路径 |
|
|
* @var string $objFile obj文件路径 |
|
|
*/ |
|
|
*/ |
|
|
public $objFile; |
|
|
|
|
|
|
|
|
private $objFile; |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* @var array 储存obj模型数据 |
|
|
* @var array 储存obj模型数据 |
|
|
*/ |
|
|
*/ |
|
|
public $objData = []; |
|
|
|
|
|
|
|
|
private $objData = [ |
|
|
|
|
|
'v' => [], // 顶点坐标数据,每行存储一个描述顶点xyz坐标的数组;
|
|
|
|
|
|
'vt' => [], // UV坐标数据,每行存储一个描述顶点 UV 坐标的数组;
|
|
|
|
|
|
'vn' => [], // 顶点法线数据,每行存储一个描述顶点xyz法线的数组;
|
|
|
|
|
|
'f' => [], // 面片数据,每行数据是对顶点坐标、顶点法线及UV坐标的素引。
|
|
|
|
|
|
]; |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* @var array 储存ply模型数据 |
|
|
|
|
|
|
|
|
* @var array 1、储存ply模型数据,Ply文件头部数据(内容包括顶点数据、面数据、线数据、材质数据或其他数据的格式及属性) |
|
|
|
|
|
* 2、顶点数据,每行数据描述了顶点的坐标、法线、及UV坐标; |
|
|
|
|
|
* 3、索引顶点数据,每行数据描述了索引顶点的个数,及顶点数据的素引。 |
|
|
*/ |
|
|
*/ |
|
|
public $plyData = []; |
|
|
|
|
|
|
|
|
private $plyData = []; |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* @var array 储存gltf模型数据 |
|
|
* @var array 储存gltf模型数据 |
|
|
*/ |
|
|
*/ |
|
|
public $gltfData = [ |
|
|
|
|
|
'asset' => '{ "version" : "2.0" }', |
|
|
|
|
|
'scene' => 0, |
|
|
|
|
|
'scenes' => [], |
|
|
|
|
|
'nodes' => [], |
|
|
|
|
|
'cameras' => [], |
|
|
|
|
|
'skins' => [], |
|
|
|
|
|
'meshes' => [], |
|
|
|
|
|
'accessors' => [], |
|
|
|
|
|
'bufferViews' => [], |
|
|
|
|
|
'buffers' => [], |
|
|
|
|
|
'material' => [], |
|
|
|
|
|
'textures' => [], |
|
|
|
|
|
'images' => [], |
|
|
|
|
|
'samplers' => [] |
|
|
|
|
|
|
|
|
private $gltfData = [ |
|
|
|
|
|
'asset' => '{}', // 用这个对象{},标识这是一个gltf文件,并说明其文件版本规范
|
|
|
|
|
|
'scene' => 0, // 用一个整数标识加载一个默认的场景
|
|
|
|
|
|
'scenes' => [], // 1、场景数组,解析一个gltf文件将从这里开始;
|
|
|
|
|
|
// 2、每一个元素值是一个对象类型,每一个对象描述了对 nodes数组 的元素索引,同时也是每一个场景的根节点;
|
|
|
|
|
|
'nodes' => [], // 节点数组,每一个元素值是一个对象类型,每一个对象描述一个 mesh(模型)、camera()或skin(蒙皮)
|
|
|
|
|
|
// 数组的元素索引;
|
|
|
|
|
|
'cameras' => [], // 相机数组,每一个元素值是一个对象类型,每一个对象是对一个相机的描述;
|
|
|
|
|
|
'skins' => [], // 蒙皮数组,每一个元素值是一个对象类型,每一个对象是对一个蒙皮的描述;
|
|
|
|
|
|
'meshes' => [], // 模型数组,每一个元素值是一个对象类型,每一个对象是对一个模型的描述;
|
|
|
|
|
|
'accessors' => [], // 访问器数组,每一个元素是一个对象类型,每一个对象描一段数据的格式或属性;
|
|
|
|
|
|
'bufferViews' => [], // 数据视图数组,每一个元素是一个对象类型,每一个对象描述一段切割数据的属性及用途;
|
|
|
|
|
|
'buffers' => [], // 缓存器数组,每一个元素是一个对象类型,每一个对象描述一个Base64编码的二进制数据缓存;
|
|
|
|
|
|
'material' => [], // 材质数组,每一个元素值是一个对象类型,每一个对象是对一个材质的描述;
|
|
|
|
|
|
'textures' => [], // 纹理数组,每一个元素值是一个对象类型,每一个对象是对一个纹理的描述;
|
|
|
|
|
|
'images' => [], // 图像数组,每一个元素值是一个对象类型,每一个对象是对一个图像的描述;
|
|
|
|
|
|
'samplers' => [] // 图像采样数组,每一个元素值是一个对象类型,每一个对象是对一个图像的描述。
|
|
|
]; |
|
|
]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -75,11 +87,11 @@ class LoardObj |
|
|
* @param string $path ply模型 保存路径 |
|
|
* @param string $path ply模型 保存路径 |
|
|
* @param string $format ply模型 保存格式 |
|
|
* @param string $format ply模型 保存格式 |
|
|
*/ |
|
|
*/ |
|
|
public function toGltf( string $path = null,string $format = self::TYPE_BINARY) |
|
|
|
|
|
|
|
|
public function toGltf( string $path = null) |
|
|
{ |
|
|
{ |
|
|
$this->createGltf(); |
|
|
$this->createGltf(); |
|
|
if( $path == null) $path = getcwd()."/gltf"; |
|
|
if( $path == null) $path = getcwd()."/gltf"; |
|
|
return $this->writeGltf( $path, $format ); |
|
|
|
|
|
|
|
|
return $this->writeGltf( $path ); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -258,7 +270,7 @@ class LoardObj |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 利用已经拆分的 obj 模型数据,生成多个 gltf 模型,并将这些模型储存在 gltfData 数组中 |
|
|
|
|
|
|
|
|
* 利用已经拆分的 obj 模型数据,生成 gltf 模型,并将这些模型数据储存在 gltfData 数组中 |
|
|
*/ |
|
|
*/ |
|
|
private function createGltf() |
|
|
private function createGltf() |
|
|
{ |
|
|
{ |
|
@ -277,23 +289,30 @@ class LoardObj |
|
|
$animation = null; |
|
|
$animation = null; |
|
|
$meshCount = 0; |
|
|
$meshCount = 0; |
|
|
$bufferCount = 0; |
|
|
$bufferCount = 0; |
|
|
$maxX = -10000000; |
|
|
|
|
|
$maxY = -10000000; |
|
|
|
|
|
$maxZ = -10000000; |
|
|
|
|
|
$minX = 10000000; |
|
|
|
|
|
$minY = 10000000; |
|
|
|
|
|
$minZ = 10000000; |
|
|
|
|
|
|
|
|
$maxX = -100000000; |
|
|
|
|
|
$maxY = -100000000; |
|
|
|
|
|
$maxZ = -100000000; |
|
|
|
|
|
$minX = 100000000; |
|
|
|
|
|
$minY = 100000000; |
|
|
|
|
|
$minZ = 100000000; |
|
|
|
|
|
|
|
|
foreach( $this->objData['meshs'] as $meshs_key => $meshs_value ) |
|
|
foreach( $this->objData['meshs'] as $meshs_key => $meshs_value ) |
|
|
{ |
|
|
{ |
|
|
$lineFace = null; |
|
|
|
|
|
$v = null; |
|
|
|
|
|
$vt = null; |
|
|
|
|
|
$vn = null; |
|
|
|
|
|
$byteOffset = 0; |
|
|
|
|
|
foreach( $meshs_value as $face_value ) // 生成顶点和面数据
|
|
|
|
|
|
|
|
|
$gltf['v'] = null; |
|
|
|
|
|
$gltf['vt'] = null; |
|
|
|
|
|
$gltf['vn'] = null; |
|
|
|
|
|
$gltf['face'] = null; |
|
|
|
|
|
$indexesList = []; |
|
|
|
|
|
$vConst = 0; |
|
|
|
|
|
foreach( $meshs_value as $face_value ) |
|
|
{ |
|
|
{ |
|
|
|
|
|
// 生成顶点和面数据
|
|
|
$face_value = implode('/', $face_value); |
|
|
$face_value = implode('/', $face_value); |
|
|
$face_value = explode('/', $face_value); |
|
|
$face_value = explode('/', $face_value); |
|
|
|
|
|
$lineFace = []; |
|
|
|
|
|
$v = null; |
|
|
|
|
|
$vt = null; |
|
|
|
|
|
$vn = null; |
|
|
foreach( $face_value as $v_key => $v_value ) |
|
|
foreach( $face_value as $v_key => $v_value ) |
|
|
{ |
|
|
{ |
|
|
switch( ($v_key+1) % 3 ) |
|
|
switch( ($v_key+1) % 3 ) |
|
@ -319,30 +338,31 @@ class LoardObj |
|
|
default: |
|
|
default: |
|
|
; |
|
|
; |
|
|
} |
|
|
} |
|
|
$v .= pack('g',(float)$xyz_value); |
|
|
|
|
|
} |
|
|
} |
|
|
$lineFace .= pack('V',((int)$v_value) - 1); |
|
|
|
|
|
|
|
|
$v = $this->objData['v'][$v_value-1]; |
|
|
break; |
|
|
break; |
|
|
|
|
|
|
|
|
case 2: |
|
|
case 2: |
|
|
if( $v_value == null ) |
|
|
|
|
|
{ |
|
|
|
|
|
$vt .= pack('g',(float)0.0000); |
|
|
|
|
|
$vt .= pack('g',(float)0.0000); |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
foreach(array_slice($this->objData['vt'][$v_value-1],0,2) as $t_value) |
|
|
|
|
|
{ |
|
|
|
|
|
$vt .= pack('g',(float)$t_value); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if( $v_value == null ) $vt = ['0.0000','0.0000']; |
|
|
|
|
|
else $vt = array_slice($this->objData['vt'][$v_value-1],0,2); |
|
|
break; |
|
|
break; |
|
|
|
|
|
|
|
|
case 0; |
|
|
case 0; |
|
|
foreach($this->objData['vn'][$v_value-1] as $n_value) |
|
|
|
|
|
|
|
|
$vn = $this->objData['vn'][$v_value-1]; |
|
|
|
|
|
$hashVar = implode(' ',$v).' '.implode(' ',$vn).' '.implode(' ',$vt); |
|
|
|
|
|
$hashVar = hash('md5', $hashVar); |
|
|
|
|
|
if( !array_key_exists($hashVar, $indexesList) ) |
|
|
{ |
|
|
{ |
|
|
$vn .= pack('g',(float)$n_value); |
|
|
|
|
|
|
|
|
foreach( $v as $valueP ) $gltf['v'] .= pack('g',(float)$valueP); |
|
|
|
|
|
foreach( $vt as $valueT ) $gltf['vt'] .= pack('g',(float)$valueT); |
|
|
|
|
|
foreach( $vn as $valueN ) $gltf['vn'] .= pack('g',(float)$valueN); |
|
|
|
|
|
$lineFace[] = $vConst; |
|
|
|
|
|
$indexesList[$hashVar] = $vConst; |
|
|
|
|
|
$vConst++; |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
$lineFace[] = $indexesList[$hashVar]; |
|
|
} |
|
|
} |
|
|
break; |
|
|
break; |
|
|
|
|
|
|
|
@ -350,9 +370,16 @@ class LoardObj |
|
|
; |
|
|
; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
foreach( $lineFace as $valueF ) $gltf['face'] .= pack('V',(int)$valueF); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
$vtnf = $v.$vn.$vt.$lineFace; |
|
|
|
|
|
|
|
|
// 生成gltf语法格式,并与顶点、面片数据组织为一个gltf模型
|
|
|
|
|
|
$vLength = strlen($gltf['v']); |
|
|
|
|
|
$vtLength = strlen($gltf['vt']); |
|
|
|
|
|
$vnLength = strlen($gltf['vn']); |
|
|
|
|
|
$vfLength = strlen($gltf['face']); |
|
|
|
|
|
|
|
|
|
|
|
$vtnf = $gltf['v'].$gltf['vn'].$gltf['vt'].$gltf['face']; |
|
|
$buffer = null; |
|
|
$buffer = null; |
|
|
$buffer = "\t\t{\n"; |
|
|
$buffer = "\t\t{\n"; |
|
|
$buffer .= "\t\t\t\"byteLength\" : ".strlen($vtnf).",\n"; |
|
|
$buffer .= "\t\t\t\"byteLength\" : ".strlen($vtnf).",\n"; |
|
@ -368,7 +395,7 @@ class LoardObj |
|
|
$byteOffset = 0; |
|
|
$byteOffset = 0; |
|
|
$bufferView = "\t\t{\n"; |
|
|
$bufferView = "\t\t{\n"; |
|
|
$bufferView .= "\t\t\t\"buffer\" : ".$meshCount.",\n"; |
|
|
$bufferView .= "\t\t\t\"buffer\" : ".$meshCount.",\n"; |
|
|
$bufferView .= "\t\t\t\"byteLength\" : ".strlen($v).",\n"; |
|
|
|
|
|
|
|
|
$bufferView .= "\t\t\t\"byteLength\" : ".$vLength.",\n"; |
|
|
$bufferView .= "\t\t\t\"byteOffset\" : ".$byteOffset.",\n"; |
|
|
$bufferView .= "\t\t\t\"byteOffset\" : ".$byteOffset.",\n"; |
|
|
$bufferView .= "\t\t\t\"target\" : 34962\n"; |
|
|
$bufferView .= "\t\t\t\"target\" : 34962\n"; |
|
|
$bufferView .= "\t\t}"; |
|
|
$bufferView .= "\t\t}"; |
|
@ -378,7 +405,7 @@ class LoardObj |
|
|
$accessor = "\t\t{\n"; |
|
|
$accessor = "\t\t{\n"; |
|
|
$accessor .= "\t\t\t\"bufferView\" : ".($bufferCount).",\n"; |
|
|
$accessor .= "\t\t\t\"bufferView\" : ".($bufferCount).",\n"; |
|
|
$accessor .= "\t\t\t\"componentType\" : 5126,\n"; |
|
|
$accessor .= "\t\t\t\"componentType\" : 5126,\n"; |
|
|
$accessor .= "\t\t\t\"count\" : ".(strlen($v) / 4 / 3).",\n"; |
|
|
|
|
|
|
|
|
$accessor .= "\t\t\t\"count\" : ".($vLength / 4 / 3).",\n"; |
|
|
$accessor .= "\t\t\t\"max\" :\n"; |
|
|
$accessor .= "\t\t\t\"max\" :\n"; |
|
|
$accessor .= "\t\t\t[\n"; |
|
|
$accessor .= "\t\t\t[\n"; |
|
|
$accessor .= "\t\t\t\t".$maxX.",\n"; |
|
|
$accessor .= "\t\t\t\t".$maxX.",\n"; |
|
@ -400,10 +427,10 @@ class LoardObj |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$bufferView = null; |
|
|
$bufferView = null; |
|
|
$byteOffset = $byteOffset + strlen($v); |
|
|
|
|
|
|
|
|
$byteOffset = $byteOffset + $vLength; |
|
|
$bufferView = "\t\t{\n"; |
|
|
$bufferView = "\t\t{\n"; |
|
|
$bufferView .= "\t\t\t\"buffer\" : ".$meshCount.",\n"; |
|
|
$bufferView .= "\t\t\t\"buffer\" : ".$meshCount.",\n"; |
|
|
$bufferView .= "\t\t\t\"byteLength\" : ".strlen($vn).",\n"; |
|
|
|
|
|
|
|
|
$bufferView .= "\t\t\t\"byteLength\" : ".$vnLength.",\n"; |
|
|
$bufferView .= "\t\t\t\"byteOffset\" : ".$byteOffset.",\n"; |
|
|
$bufferView .= "\t\t\t\"byteOffset\" : ".$byteOffset.",\n"; |
|
|
$bufferView .= "\t\t\t\"target\" : 34962\n"; |
|
|
$bufferView .= "\t\t\t\"target\" : 34962\n"; |
|
|
$bufferView .= "\t\t}"; |
|
|
$bufferView .= "\t\t}"; |
|
@ -413,7 +440,7 @@ class LoardObj |
|
|
$accessor = "\t\t{\n"; |
|
|
$accessor = "\t\t{\n"; |
|
|
$accessor .= "\t\t\t\"bufferView\" : ".($bufferCount).",\n"; |
|
|
$accessor .= "\t\t\t\"bufferView\" : ".($bufferCount).",\n"; |
|
|
$accessor .= "\t\t\t\"componentType\" : 5126,\n"; |
|
|
$accessor .= "\t\t\t\"componentType\" : 5126,\n"; |
|
|
$accessor .= "\t\t\t\"count\" : ".(strlen($vn) / 4 / 3).",\n"; |
|
|
|
|
|
|
|
|
$accessor .= "\t\t\t\"count\" : ".($vnLength / 4 / 3).",\n"; |
|
|
$accessor .= "\t\t\t\"type\" : \"VEC3\"\n"; |
|
|
$accessor .= "\t\t\t\"type\" : \"VEC3\"\n"; |
|
|
$accessor .= "\t\t}"; |
|
|
$accessor .= "\t\t}"; |
|
|
$this->gltfData['accessors'][$bufferCount] = $accessor; |
|
|
$this->gltfData['accessors'][$bufferCount] = $accessor; |
|
@ -424,10 +451,10 @@ class LoardObj |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$bufferView = null; |
|
|
$bufferView = null; |
|
|
$byteOffset = $byteOffset + strlen($vn); |
|
|
|
|
|
|
|
|
$byteOffset = $byteOffset + $vnLength; |
|
|
$bufferView = "\t\t{\n"; |
|
|
$bufferView = "\t\t{\n"; |
|
|
$bufferView .= "\t\t\t\"buffer\" : ".$meshCount.",\n"; |
|
|
$bufferView .= "\t\t\t\"buffer\" : ".$meshCount.",\n"; |
|
|
$bufferView .= "\t\t\t\"byteLength\" : ".strlen($vt).",\n"; |
|
|
|
|
|
|
|
|
$bufferView .= "\t\t\t\"byteLength\" : ".$vtLength.",\n"; |
|
|
$bufferView .= "\t\t\t\"byteOffset\" : ".$byteOffset.",\n"; |
|
|
$bufferView .= "\t\t\t\"byteOffset\" : ".$byteOffset.",\n"; |
|
|
$bufferView .= "\t\t\t\"target\" : 34962\n"; |
|
|
$bufferView .= "\t\t\t\"target\" : 34962\n"; |
|
|
$bufferView .= "\t\t}"; |
|
|
$bufferView .= "\t\t}"; |
|
@ -437,7 +464,7 @@ class LoardObj |
|
|
$accessor = "\t\t{\n"; |
|
|
$accessor = "\t\t{\n"; |
|
|
$accessor .= "\t\t\t\"bufferView\" : ".($bufferCount).",\n"; |
|
|
$accessor .= "\t\t\t\"bufferView\" : ".($bufferCount).",\n"; |
|
|
$accessor .= "\t\t\t\"componentType\" : 5126,\n"; |
|
|
$accessor .= "\t\t\t\"componentType\" : 5126,\n"; |
|
|
$accessor .= "\t\t\t\"count\" : ".(strlen($vt) / 4 / 2).",\n"; |
|
|
|
|
|
|
|
|
$accessor .= "\t\t\t\"count\" : ".($vtLength / 4 / 2).",\n"; |
|
|
$accessor .= "\t\t\t\"type\" : \"VEC2\"\n"; |
|
|
$accessor .= "\t\t\t\"type\" : \"VEC2\"\n"; |
|
|
$accessor .= "\t\t}"; |
|
|
$accessor .= "\t\t}"; |
|
|
$this->gltfData['accessors'][$bufferCount] = $accessor; |
|
|
$this->gltfData['accessors'][$bufferCount] = $accessor; |
|
@ -447,10 +474,10 @@ class LoardObj |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$bufferView = null; |
|
|
$bufferView = null; |
|
|
$byteOffset = $byteOffset + strlen($vt); |
|
|
|
|
|
|
|
|
$byteOffset = $byteOffset + $vtLength; |
|
|
$bufferView = "\t\t{\n"; |
|
|
$bufferView = "\t\t{\n"; |
|
|
$bufferView .= "\t\t\t\"buffer\" : ".$meshCount.",\n"; |
|
|
$bufferView .= "\t\t\t\"buffer\" : ".$meshCount.",\n"; |
|
|
$bufferView .= "\t\t\t\"byteLength\" : ".strlen($lineFace).",\n"; |
|
|
|
|
|
|
|
|
$bufferView .= "\t\t\t\"byteLength\" : ".$vfLength.",\n"; |
|
|
$bufferView .= "\t\t\t\"byteOffset\" : ".$byteOffset.",\n"; |
|
|
$bufferView .= "\t\t\t\"byteOffset\" : ".$byteOffset.",\n"; |
|
|
$bufferView .= "\t\t\t\"target\" : 34963\n"; |
|
|
$bufferView .= "\t\t\t\"target\" : 34963\n"; |
|
|
$bufferView .= "\t\t}"; |
|
|
$bufferView .= "\t\t}"; |
|
@ -460,7 +487,7 @@ class LoardObj |
|
|
$accessor = "\t\t{\n"; |
|
|
$accessor = "\t\t{\n"; |
|
|
$accessor .= "\t\t\t\"bufferView\" : ".($bufferCount).",\n"; |
|
|
$accessor .= "\t\t\t\"bufferView\" : ".($bufferCount).",\n"; |
|
|
$accessor .= "\t\t\t\"componentType\" : 5125,\n"; |
|
|
$accessor .= "\t\t\t\"componentType\" : 5125,\n"; |
|
|
$accessor .= "\t\t\t\"count\" : ".(strlen($lineFace) / 4).",\n"; |
|
|
|
|
|
|
|
|
$accessor .= "\t\t\t\"count\" : ".($vfLength / 4).",\n"; |
|
|
$accessor .= "\t\t\t\"type\" : \"SCALAR\"\n"; |
|
|
$accessor .= "\t\t\t\"type\" : \"SCALAR\"\n"; |
|
|
$accessor .= "\t\t}"; |
|
|
$accessor .= "\t\t}"; |
|
|
$this->gltfData['accessors'][$bufferCount] = $accessor; |
|
|
$this->gltfData['accessors'][$bufferCount] = $accessor; |
|
@ -596,7 +623,7 @@ class LoardObj |
|
|
/** |
|
|
/** |
|
|
* 将 $gltfData 数组中的gltf模型 写入硬盘 |
|
|
* 将 $gltfData 数组中的gltf模型 写入硬盘 |
|
|
*/ |
|
|
*/ |
|
|
private function writeGltf( string $path, string $format ) |
|
|
|
|
|
|
|
|
private function writeGltf( string $path ) |
|
|
{ |
|
|
{ |
|
|
if( !is_dir($path) ) |
|
|
if( !is_dir($path) ) |
|
|
{ |
|
|
{ |
|
@ -643,6 +670,5 @@ class LoardObj |
|
|
} |
|
|
} |
|
|
fwrite($handle,"}\n"); |
|
|
fwrite($handle,"}\n"); |
|
|
fclose($handle); |
|
|
fclose($handle); |
|
|
$handle = null; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |