コード例 #1
0
ファイル: LuxFilm.cpp プロジェクト: MassW/OpenMaya
void LuxRenderer::defineFilm()
{
	int width = this->mtlu_renderGlobals->imgWidth;
	int height = this->mtlu_renderGlobals->imgHeight;
	MString outputPath = this->mtlu_renderGlobals->basePath + "/" + this->mtlu_renderGlobals->imageName + "." + (int)this->mtlu_renderGlobals->currentFrame + ".lxs";
	// file path without extension, will be added automatically by the renderer
	MString fileName = this->mtlu_renderGlobals->imagePath + "/" + this->mtlu_renderGlobals->imageName + "." + (int)this->mtlu_renderGlobals->currentFrame;
	const char *filename = fileName.asChar();
	const int xres = width;
	const int yres = height;
	const bool write_png = true;
	const int halttime = this->mtlu_renderGlobals->halttime;
	const int haltspp = this->mtlu_renderGlobals->haltspp;
	int displayinterval = 3;

	ParamSet fp = CreateParamSet();
	fp->AddInt("xresolution",&xres);
	fp->AddInt("yresolution",&yres);
	fp->AddBool("write_png",&write_png);
	fp->AddString("filename",&filename);
	if( halttime > 0)
		fp->AddInt("halttime", &halttime);
	if( haltspp > 0)
		fp->AddInt("haltspp", &haltspp);
	if( displayinterval > 0)
		fp->AddInt("displayinterval", &displayinterval);
	 
	lux->film("fleximage", boost::get_pointer(fp));
}
コード例 #2
0
ファイル: LuxGeometry.cpp プロジェクト: MassW/OpenMaya
void LuxRenderer::defineTriangleMesh(mtlu_MayaObject *obj, bool noObjectDef = false)
{
	MObject meshObject = obj->mobject;
	MStatus stat = MStatus::kSuccess;
	MFnMesh meshFn(meshObject, &stat);
	
	CHECK_MSTATUS(stat);
	MItMeshPolygon faceIt(meshObject, &stat);
	CHECK_MSTATUS(stat);

	MPointArray points;
	meshFn.getPoints(points);
    MFloatVectorArray normals;
    meshFn.getNormals( normals, MSpace::kWorld );
	MFloatArray uArray, vArray;
	meshFn.getUVs(uArray, vArray);

	logger.debug(MString("Translating mesh object ") + meshFn.name().asChar());
	MString meshFullName = obj->fullNiceName;


	MIntArray trianglesPerFace, triVertices;
	meshFn.getTriangles(trianglesPerFace, triVertices);
	int numTriangles = 0;
	for( size_t i = 0; i < trianglesPerFace.length(); i++)
		numTriangles += trianglesPerFace[i];

	// lux render does not have a per vertex per face normal definition, here we can use one normal and uv per vertex only
	// So I create the triangles with unique vertices, normals and uvs. Of course this way vertices etc. cannot be shared.
	int numPTFloats = numTriangles * 3 * 3;
	logger.debug(MString("Num Triangles: ") + numTriangles + " num tri floats " + numPTFloats);

	float *floatPointArray = new float[numPTFloats];
	float *floatNormalArray = new float[numPTFloats];
	float *floatUvArray = new float[numTriangles * 3 * 2];
	
	logger.debug(MString("Allocated ") + numPTFloats + " floats for point and normals");

	MIntArray triangelVtxIdListA;
	MFloatArray floatPointArrayA;

	MPointArray triPoints;
	MIntArray triVtxIds;
	MIntArray faceVtxIds;
	MIntArray faceNormalIds;
	
	int *triangelVtxIdList = new int[numTriangles * 3];

	for( uint sgId = 0; sgId < obj->shadingGroups.length(); sgId++)
	{
		MString slotName = MString("slot_") + sgId;
	}
	
	int triCount = 0;
	int vtxCount = 0;

	for(faceIt.reset(); !faceIt.isDone(); faceIt.next())
	{
		int faceId = faceIt.index();
		int numTris;
		faceIt.numTriangles(numTris);
		faceIt.getVertices(faceVtxIds);

		MIntArray faceUVIndices;

		faceNormalIds.clear();
		for( uint vtxId = 0; vtxId < faceVtxIds.length(); vtxId++)
		{
			faceNormalIds.append(faceIt.normalIndex(vtxId));
			int uvIndex;
			faceIt.getUVIndex(vtxId, uvIndex);
			faceUVIndices.append(uvIndex);
		}

		int perFaceShadingGroup = 0;
		if( obj->perFaceAssignments.length() > 0)
			perFaceShadingGroup = obj->perFaceAssignments[faceId];
		//logger.info(MString("Face ") + faceId + " will receive SG " +  perFaceShadingGroup);

		for( int triId = 0; triId < numTris; triId++)
		{
			int faceRelIds[3];
			faceIt.getTriangle(triId, triPoints, triVtxIds);

			for( uint triVtxId = 0; triVtxId < 3; triVtxId++)
			{
				for(uint faceVtxId = 0; faceVtxId < faceVtxIds.length(); faceVtxId++)
				{
					if( faceVtxIds[faceVtxId] == triVtxIds[triVtxId])
					{
						faceRelIds[triVtxId] = faceVtxId;
					}
				}
			}

			
			uint vtxId0 = faceVtxIds[faceRelIds[0]];
			uint vtxId1 = faceVtxIds[faceRelIds[1]];
			uint vtxId2 = faceVtxIds[faceRelIds[2]];
			uint normalId0 = faceNormalIds[faceRelIds[0]];
			uint normalId1 = faceNormalIds[faceRelIds[1]];
			uint normalId2 = faceNormalIds[faceRelIds[2]];
			uint uvId0 = faceUVIndices[faceRelIds[0]];
			uint uvId1 = faceUVIndices[faceRelIds[1]];
			uint uvId2 = faceUVIndices[faceRelIds[2]];
			
			floatPointArray[vtxCount * 3] = points[vtxId0].x;
			floatPointArray[vtxCount * 3 + 1] = points[vtxId0].y;
			floatPointArray[vtxCount * 3 + 2] = points[vtxId0].z;

			floatNormalArray[vtxCount * 3] = normals[normalId0].x;
			floatNormalArray[vtxCount * 3 + 1] = normals[normalId0].y;
			floatNormalArray[vtxCount * 3 + 2] = normals[normalId0].z;

			floatUvArray[vtxCount * 2] = uArray[uvId0];
			floatUvArray[vtxCount * 2 + 1] = vArray[uvId0];

			vtxCount++;

			floatPointArray[vtxCount * 3] = points[vtxId1].x;
			floatPointArray[vtxCount * 3 + 1] = points[vtxId1].y;
			floatPointArray[vtxCount * 3 + 2] = points[vtxId1].z;

			floatNormalArray[vtxCount * 3] = normals[normalId1].x;
			floatNormalArray[vtxCount * 3 + 1] = normals[normalId1].y;
			floatNormalArray[vtxCount * 3 + 2] = normals[normalId1].z;

			floatUvArray[vtxCount * 2] = uArray[uvId1];
			floatUvArray[vtxCount * 2 + 1] = vArray[uvId1];

			vtxCount++;

			floatPointArray[vtxCount * 3] = points[vtxId2].x;
			floatPointArray[vtxCount * 3 + 1] = points[vtxId2].y;
			floatPointArray[vtxCount * 3 + 2] = points[vtxId2].z;

			floatNormalArray[vtxCount * 3] = normals[normalId2].x;
			floatNormalArray[vtxCount * 3 + 1] = normals[normalId2].y;
			floatNormalArray[vtxCount * 3 + 2] = normals[normalId2].z;

			floatUvArray[vtxCount * 2] = uArray[uvId2];
			floatUvArray[vtxCount * 2 + 1] = vArray[uvId2];

			vtxCount++;
			
			//logger.debug(MString("Vertex count: ") + vtxCount + " maxId " + ((vtxCount - 1) * 3 + 2) + " ptArrayLen " + (numTriangles * 3 * 3));

			triangelVtxIdList[triCount * 3] = triCount * 3;
			triangelVtxIdList[triCount * 3 + 1] = triCount * 3 + 1;
			triangelVtxIdList[triCount * 3 + 2] = triCount * 3 + 2;

			triCount++;
		}		
	}

//generatetangents 	bool 	Generate tangent space using miktspace, useful if mesh has a normal map that was also baked using miktspace (such as blender or xnormal) 	false
//subdivscheme 	string 	Subdivision algorithm, options are "loop" and "microdisplacement" 	"loop"
//displacementmap 	string 	Name of the texture used for the displacement. Subdivscheme parameter must always be provided, as load-time displacement is handled by the loop-subdivision code. 	none - optional. (loop subdiv can be used without displacement, microdisplacement will not affect the mesh without a displacement map specified)
//dmscale 	float 	Scale of the displacement (for an LDR map, this is the maximum height of the displacement in meter) 	0.1
//dmoffset 	float 	Offset of the displacement. 	0
//dmnormalsmooth 	bool 	Smoothing of the normals of the subdivided faces. Only valid for loop subdivision. 	true
//dmnormalsplit 	bool 	Force the mesh to split along breaks in the normal. If a mesh has no normals (flat-shaded) it will rip open on all edges. Only valid for loop subdivision. 	false
//dmsharpboundary 	bool 	Try to preserve mesh boundaries during subdivision. Only valid for loop subdivision. 	false
//nsubdivlevels 	integer 	Number of subdivision levels. This is only recursive for loop subdivision, microdisplacement will need much larger values (such as 50). 	0

	bool generatetangents = false;
	getBool(MString("mtlu_mesh_generatetangents"), meshFn, generatetangents);
	int subdivscheme = 0;
	const char *subdAlgos[] = {"loop", "microdisplacement"};
	getInt(MString("mtlu_mesh_subAlgo"), meshFn, subdivscheme);
	const char *subdalgo =  subdAlgos[subdivscheme];
	float dmscale;
	getFloat(MString("mtlu_mesh_dmscale"), meshFn, dmscale);
	float dmoffset;
	getFloat(MString("mtlu_mesh_dmoffset"), meshFn, dmoffset);
	MString displacementmap;
	getString(MString("mtlu_mesh_displacementMap"), meshFn, displacementmap);
	const char *displacemap = displacementmap.asChar();
	bool dmnormalsmooth = true;
	getBool(MString("mtlu_mesh_dmnormalsmooth"), meshFn, dmnormalsmooth);
	bool dmnormalsplit = false;
	getBool(MString("mtlu_mesh_dmnormalsplit"), meshFn, dmnormalsplit);
	bool dmsharpboundary = false;
	getBool(MString("mtlu_mesh_dmsharpboundary"), meshFn, dmsharpboundary);
	int nsubdivlevels = 0;
	getInt(MString("mtlu_mesh_subdivlevel"), meshFn, nsubdivlevels);

	// a displacment map needs its own texture defintion
	MString displacementTextureName = "";
	if(displacementmap.length() > 0)
	{
		ParamSet dmParams = CreateParamSet();
		dmParams->AddString("filename", &displacemap);
		displacementTextureName = meshFn.name() + "_displacementMap";
		this->lux->texture(displacementTextureName.asChar(), "float", "imagemap", boost::get_pointer(dmParams));
	}

	ParamSet triParams = CreateParamSet();
	int numPointValues = numTriangles * 3;
	int numUvValues = numTriangles * 3 * 2;
	clock_t startTime = clock();
	logger.info(MString("Adding mesh values to params."));
	triParams->AddInt("indices", triangelVtxIdList, numTriangles * 3);
	triParams->AddPoint("P", floatPointArray, numPointValues);
	triParams->AddNormal("N", floatNormalArray, numPointValues);
	triParams->AddFloat("uv",  floatUvArray, numUvValues);
	if( nsubdivlevels > 0)
		triParams->AddInt("nsubdivlevels", &nsubdivlevels, 1);
	triParams->AddBool("generatetangents",  &generatetangents, 1);
	triParams->AddString("subdivscheme", &subdalgo , 1);
	if(displacementmap.length() > 0)
	{
		triParams->AddFloat("dmoffset",  &dmoffset, 1);
		triParams->AddFloat("dmscale",  &dmscale, 1);
		const char *dmft = displacementTextureName.asChar();
		triParams->AddString("displacementmap", &dmft);
	}
	triParams->AddBool("dmnormalsmooth",  &dmnormalsmooth, 1);
	triParams->AddBool("dmnormalsplit",  &dmnormalsplit, 1);
	triParams->AddBool("dmsharpboundary",  &dmsharpboundary, 1);


	clock_t pTime = clock();
	if(!noObjectDef)
		this->lux->objectBegin(meshFullName.asChar());
	this->lux->shape("trianglemesh", boost::get_pointer(triParams));
	if(!noObjectDef)
		this->lux->objectEnd();

	clock_t eTime = clock();
	logger.info(MString("Timing: Parameters: ") + ((pTime - startTime)/CLOCKS_PER_SEC) + " objTime " + ((eTime - pTime)/CLOCKS_PER_SEC) + " all " + ((eTime - startTime)/CLOCKS_PER_SEC));

	return;

}
コード例 #3
0
//static void processFiles(ParamSet &params, std::set<string> &tmpFiles, std::iostream &stream) {
static void processFiles(ParamSet &params, socket_stream_t &stream) {
	LOG(LUX_DEBUG,LUX_NOERROR) << "Receiving file index";

	string s = get_response(stream);
	if (s == "FILE INDEX EMPTY") {
		LOG(LUX_DEBUG,LUX_NOERROR) << "No files";
		return;
	}

	if (s != "BEGIN FILE INDEX") {
		throw std::runtime_error("Expected 'BEGIN FILE INDEX', got '" + s + "'");
	}

	stream << "BEGIN FILE INDEX OK" << "\n";

	vector<std::pair<string, string> > neededFiles;

	while (true) {
		string paramName = get_response(stream);
		if (paramName == "END FILE INDEX") {
			LOG(LUX_DEBUG,LUX_NOERROR) << "End of file index";
			break;
		}

		string filename = get_response(stream);
		string hash = get_response(stream);
		string empty = get_response(stream); // empty line
		
		if (paramName == "" || filename == "" || hash == "" || empty != "") {
			LOG( LUX_ERROR,LUX_SYSTEM)<< "Invalid file index entry " 
				<< "param: '" << paramName << "', "
				<< "filename: '" << filename << "', "
				<< "hash: '" << hash << "', "
				<< "empty: '" << empty << "'";
			stream << "FILE INDEX INVALID" << "\n";
			return;
		}

		LOG(LUX_DEBUG,LUX_NOERROR) << "File param '" << paramName << "', filename '" << filename << "', hash '" << hash << "'";

		boost::filesystem::path fname(filename);

		boost::filesystem::path tfile("tmp_" + hash);
		tfile.replace_extension(fname.extension());

		//if (tmpFiles.find(tfile.string()) == tmpFiles.end()) {
		boost::system::error_code ec;
		if (!boost::filesystem::exists(tfile, ec)) {
			LOG( LUX_INFO,LUX_NOERROR) << "Requesting file '" << filename << "' (as '" << tfile.string() << "')";
			neededFiles.push_back(std::make_pair(hash, tfile.string()));
		} else {
			LOG( LUX_DEBUG,LUX_NOERROR) << "Using existing file  '" << filename << "' (as '" << tfile.string() << "')";
		}
		
		// replace parameter
		params.AddString(paramName, &tfile.string());
	}

	stream << "END FILE INDEX OK" << "\n";

	// now lets grab the files we need
	if (!read_response(stream, "BEGIN FILES"))
		return;
	stream << "BEGIN FILES OK" << "\n";

	for (size_t i = 0; i < neededFiles.size(); i++) {
		const string& hash(neededFiles[i].first);
		const string& fname(neededFiles[i].second);
		stream << hash << endl << flush;
		if (!receiveFile(fname, hash, stream)) {
			stream << "RESEND FILE" << endl << flush;
			if (!receiveFile(fname, hash, stream))
				throw std::runtime_error("Error receiving file '" + fname + "'");
		}
		stream << "FILE OK" << "\n";
		//tmpFiles.insert(neededFiles[i].second);
	}

	stream << "END FILES" << "\n";

	if (!read_response(stream, "END FILES OK"))
		return;
}
コード例 #4
0
ファイル: LuxCamera.cpp プロジェクト: haggi/OpenMaya
void LuxRenderer::defineCamera()
{
	std::shared_ptr<MayaScene> mayaScene = MayaTo::getWorldPtr()->worldScenePtr;
	std::shared_ptr<RenderGlobals> renderGlobals = MayaTo::getWorldPtr()->worldRenderGlobalsPtr;
	std::shared_ptr<MayaObject> mo = mayaScene->camList[0];

	MMatrix cm = mo->dagPath.inclusiveMatrix();
	MFnCamera camFn(mo->mobject);
	this->transformCamera(mo.get(), renderGlobals->doMb && (mo->transformMatrices.size() > 1));

	// lux uses the fov of the smallest image edge
	double hFov = RadToDeg(camFn.horizontalFieldOfView());
	double vFov = RadToDeg(camFn.verticalFieldOfView());
	float fov = hFov;
	int width, height;
	renderGlobals->getWidthHeight(width, height);
	if( height < width)
		fov = vFov;

	// focaldist
	float focusDist = (float)camFn.focusDistance() * renderGlobals->sceneScale;
	float focalLen = (float)camFn.focalLength();
	float fStop = (float)camFn.fStop();

	bool useDOF = false;
	getBool(MString("depthOfField"), camFn, useDOF);
	useDOF = useDOF && renderGlobals->doDof;

	// hither, yon
	float hither = (float)camFn.nearClippingPlane();
	float yon = (float)camFn.farClippingPlane();
	
	// render region
	int left, bottom, right, top;
	renderGlobals->getRenderRegion(left, bottom, right, top);
	int ybot = (height - bottom);
	int ytop = (height - top);
	int ymin = ybot <  ytop ? ybot :  ytop;
	int ymax = ybot >  ytop ? ybot :  ytop;
	float lensradius = (focalLen / 1000.0) / ( 2.0 * fStop );

	int blades = 0;
	getInt(MString("mtlu_diaphragm_blades"), camFn, blades);
	bool autofocus = false;
	getBool(MString("mtlu_autofocus"), camFn, autofocus);
	int dist = 0;
	getInt(MString("mtlu_distribution"), camFn, dist);
	logger.debug(MString("Lens distribution: ") + dist + " " + LensDistributions[dist]);
	float power = 1.0f;
	getFloat(MString("mtlu_power"), camFn, power);
	const char *lensdistribution =  LensDistributions[dist];

	float shutterOpen = 0.0f;
	float shutterClose = renderGlobals->mbLength;

	ParamSet cp = CreateParamSet();
	cp->AddFloat("fov", &fov);
	cp->AddFloat("focaldistance", &focusDist);
	cp->AddFloat("hither", &hither);
	cp->AddFloat("yon", &yon);
	cp->AddFloat("shutteropen", &shutterOpen);
	cp->AddFloat("shutterclose", &shutterClose);
	if( blades > 0)
		cp->AddInt("blades", &blades);
	if( useDOF )
	{
		cp->AddFloat("lensradius", &lensradius);
		cp->AddBool("autofocus", &autofocus);
		cp->AddString("distribution", &lensdistribution);
		cp->AddFloat("power", &power);
	}
	lux->camera("perspective", boost::get_pointer(cp));
	
	if( renderGlobals->exportSceneFile)
		this->luxFile << "Camera \"perspective\" "<< "\"float fov\" [" << fov << "]"  <<"\n";

}
コード例 #5
0
ファイル: pbrt.cpp プロジェクト: mhietajarvi/kalajoki2015
int testSimpleScene() {

	ParamSet params;

	int xres = 500;
	int yres = 500;
	params.AddInt("xresolution", &xres, 1);
	params.AddInt("yresolution", &yres, 1);

	Transform t = LookAt(Point(0,0,0), Point(0,0,-100), Vector(0,1,0));

	AnimatedTransform cam2world(&t, 0, &t, 0);

	params.AddString("filename", new string("render.png"), 1);
	//BoxFilter *filter = CreateBoxFilter(params);
	GaussianFilter *filter = new GaussianFilter(3, 3, 0.001f);


	ImageFilm *film = CreateImageFilm(params, filter);
	PerspectiveCamera *camera = CreatePerspectiveCamera(params, cam2world, film);

	//AdaptiveSampler *sampler = CreateAdaptiveSampler(params, film, camera);
	//Sampler *sampler = CreateRandomSampler(params, film, camera);
	//Sampler *sampler = CreateBestCandidateSampler(params, film, camera);
	//Sampler *sampler = CreateHaltonSampler(params, film, camera);
	//StratifiedSampler *sampler = CreateStratifiedSampler(params, film, camera);

    bool jitter = false;
    int xstart, xend, ystart, yend;
    film->GetSampleExtent(&xstart, &xend, &ystart, &yend);
    int xsamp = 1;
    int ysamp = 1;
    StratifiedSampler *sampler = new StratifiedSampler(
		xstart, xend, ystart, yend,
		xsamp, ysamp,
        jitter, camera->shutterOpen, camera->shutterClose);

	//PathIntegrator *surfaceIg = CreatePathSurfaceIntegrator(params);
	//DirectLightingIntegrator *surfaceIg = CreateDirectLightingIntegrator(params);
	WhittedIntegrator *surfaceIg = CreateWhittedSurfaceIntegrator(params);

	SingleScatteringIntegrator *volumeIg = CreateSingleScatteringIntegrator(params);

    SamplerRenderer renderer(sampler, camera, surfaceIg, volumeIg, false);

	VolumeRegion *volumeRegion = NULL;

	vector<Light*> lights;

	float il1[] = {1.f,1.f,1.f};
	float il2[] = {2.f,0.5f,0.3f};
	float il3[] = {0.f,0.2f,1.3f};

	lights.push_back(new PointLight(Translate(Vector(2,2,0)), RGBSpectrum::FromRGB(il1)));
	lights.push_back(new PointLight(Translate(Vector(-2,-2,-2)), RGBSpectrum::FromRGB(il2)));
	lights.push_back(new PointLight(Translate(Vector(-2, 2,-2)), RGBSpectrum::FromRGB(il3)));
	//(MCreatePointLight(Translate(Vector(2,2,0)), params));
	//lights.push_back(CreatePointLight(Translate(Vector(0,4,0)), params));

	Transform obj2world = Translate(Vector(0,0,-2));
	Transform world2obj = Inverse(obj2world);
	Sphere *sphere1 = CreateSphereShape(&obj2world, &world2obj, false, params);

	Transform obj2world2 = Translate(Vector(0.7,0.7,2.6));
	Transform world2obj2 = Inverse(obj2world2);
	Sphere *sphere2 = CreateSphereShape(&obj2world2, &world2obj2, false, params);

    TextureParams tparams(params, params, map<string, Reference<Texture<float> > >(),
		map<string, Reference<Texture<Spectrum> > >());

	MirrorMaterial *mirror = new MirrorMaterial(new ConstantTexture<Spectrum>(Spectrum(0.9f)), NULL);

	ShinyMetalMaterial *metal = new ShinyMetalMaterial(
		new ConstantTexture<Spectrum>(Spectrum(1.f)),
		new ConstantTexture<float>(0.1f),
		new ConstantTexture<Spectrum>(Spectrum(1.f)),
		NULL); //CreateShinyMetalMaterial(Transform(), tparams);
	GlassMaterial *glass = CreateGlassMaterial(Transform(), tparams);

	//float c1[] = {0.f,10.99f,0.f};
	float c1[] = {5.f,5.f,5.f};
	Spectrum spec1 = RGBSpectrum::FromRGB(c1, SpectrumType::SPECTRUM_REFLECTANCE);

	MatteMaterial *matte = new MatteMaterial(
		new ConstantTexture<Spectrum>(spec1),
		new ConstantTexture<float>(0.0f), NULL);
		
  //  Reference<Texture<Spectrum> > Kd = mp.GetSpectrumTexture("Kd", Spectrum(0.5f));
  //  Reference<Texture<float> > sigma = mp.GetFloatTexture("sigma", 0.f);
  //  Reference<Texture<float> > bumpMap = mp.GetFloatTextureOrNull("bumpmap");
  //  return ;		
		//CreateMatteMaterial(Transform(), tparams);

    Reference<Primitive> prim1 = new GeometricPrimitive(sphere1, matte, NULL);
    //Reference<Primitive> prim2 = new GeometricPrimitive(sphere2, metal, NULL);

	vector<Reference<Primitive> > prims;
	prims.push_back(prim1);
	//prims.push_back(prim2);
    Primitive *accel = CreateBVHAccelerator(prims, params);

    Scene *scene = new Scene(accel, lights, volumeRegion);

//    Scene(Primitive *accel, const vector<Light *> &lts, VolumeRegion *vr);

//TODO: 
//	create scene
//	step through to see that something happens
//	start building minimal viable js version
//	- unit test js functionality against similar c++ unit results


	renderer.Render(scene);
	film->WriteImage(1);

	return 0;
}