示例#1
0
float RenderTerrain::getHeightAtPoint( long x, long y ) const
{
	x = Math::Min(x, (long)m_size - 1L);
	x = Math::Max(x, 0L);
	y = Math::Min(y, (long)m_size - 1L);
	y = Math::Max(y, 0L);

	return *getHeightData(x, y);
}
示例#2
0
void OgreTerrainPageBridge::terrainPageReady()
{
	std::shared_ptr<float> heightDataPtr = mHeightData;
	// No need to keep height data around since it is copied on call.
	//Note that the data won't be deleted until heightDataPtr runs out of scope, unless updateTerrain(...) is active at the same time.
	mHeightData.reset();
	S_LOG_INFO("Finished loading or updating terrain page geometry: [" << mIndex.first << "," << mIndex.second << "]");
	if (heightDataPtr) {
		auto terrain = mTerrainGroup.getTerrain(mIndex.first, mIndex.second);
		if (terrain && terrain->getHeightData()) {
			float* heightData = terrain->getHeightData();
			memcpy(heightData, heightDataPtr.get(), sizeof(float) * terrain->getSize() * terrain->getSize());
			terrain->dirty();
			terrain->update();
		} else {
			mTerrainGroup.defineTerrain(mIndex.first, mIndex.second, heightDataPtr.get());
		}

	}
	heightDataPtr.reset();
	// Notify waiting threads such as OgreTerrainDefiner
	mConditionVariable.notify_all();
}
示例#3
0
void Brush::applyHeightBrush(long int x, long int y, const Ogre::Vector3& position, float average)
{
    auto ter = m_group->getTerrain(x, y);
    if(!ter)
        return;
    auto indices = (position - ter->getPosition())/ter->getWorldSize();
    indices += 0.5;
    auto cmd = dynamic_cast<undo::HeightmapEdit*>(m_currentCommand);
    assert(cmd != nullptr);
    indices.z = 1.0f - indices.z;
    indices *= ter->getSize();
    float* heightdata = ter->getHeightData();
    float brushSize = m_settings[m_mode].size/ter->getWorldSize() * ter->getSize();
    size_t terSize = ter->getSize();
    // LEFT  TOP  RIGHT  BOTTOM
    Ogre::Rect rect(Ogre::Math::Clamp<int>(indices.x-brushSize/2, 0, terSize),  // left
                    Ogre::Math::Clamp<int>(indices.z-brushSize/2, 0, terSize),  // top
                    Ogre::Math::Clamp<int>(indices.x+brushSize/2, 0, terSize),  // right
                    Ogre::Math::Clamp<int>(indices.z+brushSize/2, 0, terSize)); // bottom
    std::cout << rect << std::endl;
    if(rect.bottom == rect.top || rect.left == rect.right)
        // brush does not reach this terrain
        return;
    cmd->monitorTerrain(x, y, rect);
    if(m_mode == M_HEIGHT_RAISE) {
        float strength = m_settings[m_mode].strength *
                        ((QApplication::queryKeyboardModifiers()
                        & Qt::ShiftModifier)? -0.1f : 0.1f);
        for(int ix=rect.left; ix<rect.right; ix++) {
            for(int iy=rect.top; iy<rect.bottom; iy++) {
                float dist = std::sqrt(std::pow(ix-indices.x, 2) + std::pow(iy-indices.z, 2));
                heightdata[iy*terSize+ix] += brushFunction(dist/brushSize*2,
                                                                m_settings[m_mode].hardness) * strength;
            }
        }
    } else if(m_mode == M_HEIGHT_SMOOTH) {
        for(int ix=rect.left; ix<rect.right; ix++) {
            for(int iy=rect.top; iy<rect.bottom; iy++) {
                float dist = std::sqrt(std::pow(ix-indices.x, 2) + std::pow(iy-indices.z, 2));
                float weight = brushFunction(dist/brushSize*2, m_settings[m_mode].hardness);
                float difference = average - heightdata[iy*terSize+ix];
                difference *= m_settings[m_mode].strength/10.0f;
                heightdata[iy*terSize+ix] += weight*difference;
            }
        }
    } else if(m_mode == M_HEIGHT_FLATTEN) {
        if(QApplication::queryKeyboardModifiers() & Qt::ShiftModifier) {
            m_height = position.y;
            m_drawer->setBrushOptions(m_settings[m_mode].size,
                                        m_settings[m_mode].hardness,
                                        m_height);
        } else {
            for(int ix=rect.left; ix<rect.right; ix++) {
                for(int iy=rect.top; iy<rect.bottom; iy++) {
                    float dist = std::sqrt(std::pow(ix-indices.x, 2) + std::pow(iy-indices.z, 2));
                    float weight = brushFunction(dist/brushSize*2, m_settings[m_mode].hardness);
                    float difference = m_height - heightdata[iy*terSize+ix];
                    difference *= m_settings[m_mode].strength/10.0f;
                    heightdata[iy*terSize+ix] += weight*difference;
                }
            }
        }
    }
    ter->dirtyRect(rect);
    ter->updateGeometryWithoutNotifyNeighbours();
    ter->update();
}
示例#4
0
/// @par
///
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// @see rcAllocPolyMeshDetail, rcPolyMesh, rcCompactHeightfield, rcPolyMeshDetail, rcConfig
bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompactHeightfield& chf,
						   const float sampleDist, const float sampleMaxError,
						   rcPolyMeshDetail& dmesh)
{
	rcAssert(ctx);
	
	ctx->startTimer(RC_TIMER_BUILD_POLYMESHDETAIL);
	
	if (mesh.nverts == 0 || mesh.npolys == 0)
		return true;
	
	const int nvp = mesh.nvp;
	const float cs = mesh.cs;
	const float ch = mesh.ch;
	const float* orig = mesh.bmin;
	const int borderSize = mesh.borderSize;
	
	rcIntArray edges(64);
	rcIntArray tris(512);
	rcIntArray stack(512);
	rcIntArray samples(512);
	float verts[256*3];
	rcHeightPatch hp;
	int nPolyVerts = 0;
	int maxhw = 0, maxhh = 0;
	
	rcScopedDelete<int> bounds = (int*)rcAlloc(sizeof(int)*mesh.npolys*4, RC_ALLOC_TEMP);
	if (!bounds)
	{
		ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'bounds' (%d).", mesh.npolys*4);
		return false;
	}
	rcScopedDelete<float> poly = (float*)rcAlloc(sizeof(float)*nvp*3, RC_ALLOC_TEMP);
	if (!poly)
	{
		ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'poly' (%d).", nvp*3);
		return false;
	}
	
	// Find max size for a polygon area.
	for (int i = 0; i < mesh.npolys; ++i)
	{
		const unsigned short* p = &mesh.polys[i*nvp*2];
		int& xmin = bounds[i*4+0];
		int& xmax = bounds[i*4+1];
		int& ymin = bounds[i*4+2];
		int& ymax = bounds[i*4+3];
		xmin = chf.width;
		xmax = 0;
		ymin = chf.height;
		ymax = 0;
		for (int j = 0; j < nvp; ++j)
		{
			if(p[j] == RC_MESH_NULL_IDX) break;
			const unsigned short* v = &mesh.verts[p[j]*3];
			xmin = rcMin(xmin, (int)v[0]);
			xmax = rcMax(xmax, (int)v[0]);
			ymin = rcMin(ymin, (int)v[2]);
			ymax = rcMax(ymax, (int)v[2]);
			nPolyVerts++;
		}
		xmin = rcMax(0,xmin-1);
		xmax = rcMin(chf.width,xmax+1);
		ymin = rcMax(0,ymin-1);
		ymax = rcMin(chf.height,ymax+1);
		if (xmin >= xmax || ymin >= ymax) continue;
		maxhw = rcMax(maxhw, xmax-xmin);
		maxhh = rcMax(maxhh, ymax-ymin);
	}
	
	hp.data = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxhw*maxhh, RC_ALLOC_TEMP);
	if (!hp.data)
	{
		ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'hp.data' (%d).", maxhw*maxhh);
		return false;
	}
	
	dmesh.nmeshes = mesh.npolys;
	dmesh.nverts = 0;
	dmesh.ntris = 0;
	dmesh.meshes = (unsigned int*)rcAlloc(sizeof(unsigned int)*dmesh.nmeshes*4, RC_ALLOC_PERM);
	if (!dmesh.meshes)
	{
		ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.meshes' (%d).", dmesh.nmeshes*4);
		return false;
	}
	
	int vcap = nPolyVerts+nPolyVerts/2;
	int tcap = vcap*2;
	
	dmesh.nverts = 0;
	dmesh.verts = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM);
	if (!dmesh.verts)
	{
		ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.verts' (%d).", vcap*3);
		return false;
	}
	dmesh.ntris = 0;
	dmesh.tris = (unsigned char*)rcAlloc(sizeof(unsigned char)*tcap*4, RC_ALLOC_PERM);
	if (!dmesh.tris)
	{
		ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' (%d).", tcap*4);
		return false;
	}
	
	for (int i = 0; i < mesh.npolys; ++i)
	{
		const unsigned short* p = &mesh.polys[i*nvp*2];
		
		// Store polygon vertices for processing.
		int npoly = 0;
		for (int j = 0; j < nvp; ++j)
		{
			if(p[j] == RC_MESH_NULL_IDX) break;
			const unsigned short* v = &mesh.verts[p[j]*3];
			poly[j*3+0] = v[0]*cs;
			poly[j*3+1] = v[1]*ch;
			poly[j*3+2] = v[2]*cs;
			npoly++;
		}
		
		// Get the height data from the area of the polygon.
		hp.xmin = bounds[i*4+0];
		hp.ymin = bounds[i*4+2];
		hp.width = bounds[i*4+1]-bounds[i*4+0];
		hp.height = bounds[i*4+3]-bounds[i*4+2];
		getHeightData(chf, p, npoly, mesh.verts, borderSize, hp, stack, mesh.regs[i]);
		
		// Build detail mesh.
		int nverts = 0;
		if (!buildPolyDetail(ctx, poly, npoly,
							 sampleDist, sampleMaxError,
							 chf, hp, verts, nverts, tris,
							 edges, samples))
		{
			return false;
		}
		
		// Move detail verts to world space.
		for (int j = 0; j < nverts; ++j)
		{
			verts[j*3+0] += orig[0];
			verts[j*3+1] += orig[1] + chf.ch; // Is this offset necessary?
			verts[j*3+2] += orig[2];
		}
		// Offset poly too, will be used to flag checking.
		for (int j = 0; j < npoly; ++j)
		{
			poly[j*3+0] += orig[0];
			poly[j*3+1] += orig[1];
			poly[j*3+2] += orig[2];
		}
		
		// Store detail submesh.
		const int ntris = tris.size()/4;
		
		dmesh.meshes[i*4+0] = (unsigned int)dmesh.nverts;
		dmesh.meshes[i*4+1] = (unsigned int)nverts;
		dmesh.meshes[i*4+2] = (unsigned int)dmesh.ntris;
		dmesh.meshes[i*4+3] = (unsigned int)ntris;
		
		// Store vertices, allocate more memory if necessary.
		if (dmesh.nverts+nverts > vcap)
		{
			while (dmesh.nverts+nverts > vcap)
				vcap += 256;
			
			float* newv = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM);
			if (!newv)
			{
				ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newv' (%d).", vcap*3);
				return false;
			}
			if (dmesh.nverts)
				memcpy(newv, dmesh.verts, sizeof(float)*3*dmesh.nverts);
			rcFree(dmesh.verts);
			dmesh.verts = newv;
		}
		for (int j = 0; j < nverts; ++j)
		{
			dmesh.verts[dmesh.nverts*3+0] = verts[j*3+0];
			dmesh.verts[dmesh.nverts*3+1] = verts[j*3+1];
			dmesh.verts[dmesh.nverts*3+2] = verts[j*3+2];
			dmesh.nverts++;
		}
		
		// Store triangles, allocate more memory if necessary.
		if (dmesh.ntris+ntris > tcap)
		{
			while (dmesh.ntris+ntris > tcap)
				tcap += 256;
			unsigned char* newt = (unsigned char*)rcAlloc(sizeof(unsigned char)*tcap*4, RC_ALLOC_PERM);
			if (!newt)
			{
				ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newt' (%d).", tcap*4);
				return false;
			}
			if (dmesh.ntris)
				memcpy(newt, dmesh.tris, sizeof(unsigned char)*4*dmesh.ntris);
			rcFree(dmesh.tris);
			dmesh.tris = newt;
		}
		for (int j = 0; j < ntris; ++j)
		{
			const int* t = &tris[j*4];
			dmesh.tris[dmesh.ntris*4+0] = (unsigned char)t[0];
			dmesh.tris[dmesh.ntris*4+1] = (unsigned char)t[1];
			dmesh.tris[dmesh.ntris*4+2] = (unsigned char)t[2];
			dmesh.tris[dmesh.ntris*4+3] = getTriFlags(&verts[t[0]*3], &verts[t[1]*3], &verts[t[2]*3], poly, npoly);
			dmesh.ntris++;
		}
	}
	
	ctx->stopTimer(RC_TIMER_BUILD_POLYMESHDETAIL);
	
	return true;
}
示例#5
0
void RenderTerrain::getPoint( long x, long y, Vector3* outpos )
{
	getPoint(x, y, *getHeightData(x, y), outpos);
}
示例#6
0
bool HelloWorld::init()
{
    if ( !Layer::init() )
    {
        return false;
    }
    
    auto visibleSize = Director::getInstance()->getVisibleSize();
    auto origin = Director::getInstance()->getVisibleOrigin();



	camera = Camera::createPerspective(30, (float)visibleSize.width / visibleSize.height, 1.0, 1000);
	camera->setBackgroundBrush(CameraBackgroundBrush::createColorBrush(Color4F(0.0f, 1.0f, 1.0f, 0.5f), 1.0f));
	camera->setPosition3D(Vec3(0.0f, 50.0f, 100.0f));
	camera->lookAt(Vec3(0.0f, 0.0f, 0.0f), Vec3(0.0, 1.0, 0.0));
	camera->setCameraFlag(CameraFlag::USER2);
	this->addChild(camera);

	/**
	//Create Plane
	Physics3DRigidBodyDes rbd_plan;
	rbd_plan.mass = 0.0f;
	rbd_plan.shape = Physics3DShape::createBox(Vec3(500, 5.0f, 500));
	auto physics_rbd_plan = Physics3DRigidBody::create(&rbd_plan);
	physics_rbd_plan->setFriction(20);
	auto component_plan = Physics3DComponent::create(physics_rbd_plan);
	_plan = Sprite3D::create("box.c3t");
	_plan->setTexture("plane.png");
	_plan->setScale(30.0f);
	_plan->setPosition3D(Vec3(visibleSize.width / 2 + origin.x, visibleSize.height / 2 + origin.y, 0));
	_plan->setScaleX(500);
	_plan->setScaleY(5);
	_plan->setScaleZ(500);
	_plan->setPositionZ(0);
	_plan->setPositionY(0);
	_plan->setPositionX(0);
	_plan->setGlobalZOrder(-1);
	_plan->addComponent(component_plan);
	component_plan->syncNodeToPhysics();
	component_plan->setSyncFlag(Physics3DComponent::PhysicsSyncFlag::NONE);
	_plan->setCameraMask((unsigned short)CameraFlag::USER2);
	this->addChild(_plan, 1);
	**/
	Terrain::DetailMap r("dirt.jpg");
	Terrain::TerrainData data("heightmap16.jpg", "alphamap.png", r, r, r, r, Size(32, 32), 5.0f, 1.0f);
	auto _terrain = Terrain::create(data, Terrain::CrackFixedType::SKIRT);
	_terrain->setPosition3D(Vec3(visibleSize.width / 2 + origin.x, visibleSize.height / 2 + origin.y, 0));
	_terrain->setPositionZ(0);
	_terrain->setPositionY(0);
	_terrain->setPositionX(0);
	_terrain->setLODDistance(64, 128, 192);
	_terrain->setDrawWire(false);
	_terrain->setMaxDetailMapAmount(4);
	_terrain->setSkirtHeightRatio(2);
	_terrain->setCameraMask((unsigned short)CameraFlag::USER2);
	//create terrain
	std::vector<float> heidata = _terrain->getHeightData();
	auto size = _terrain->getTerrainSize();
	Physics3DColliderDes colliderDes;
	colliderDes.shape = Physics3DShape::createHeightfield(size.width, size.height, &heidata[0], 1.0f, _terrain->getMinHeight(), _terrain->getMaxHeight(), true, false, true);
	auto collider = Physics3DCollider::create(&colliderDes);
	auto component = Physics3DComponent::create(collider);
	_terrain->addComponent(component);
	component->syncNodeToPhysics();
	component->setSyncFlag(Physics3DComponent::PhysicsSyncFlag::NONE);
	this->addChild(_terrain);

	
	//////////////////////////////////////////////// car
	Physics3DRigidBodyDes rbd_cabine;
	rbd_cabine.disableSleep = true;
	rbd_cabine.mass = 500.0f;
	rbd_cabine.shape = Physics3DShape::createBox(Vec3(4, 2.0f, 8));
	physics_rbd_cabine = Physics3DRigidBody::create(&rbd_cabine);
	auto cabine_component = Physics3DComponent::create(physics_rbd_cabine);
	car_cabine = Sprite3D::create("T-90.c3t");
	car_cabine->setTexture("Main Body 2.png");
	car_cabine->setScale(3);
	//car_cabine->setPosition3D(Vec3(visibleSize.width / 2 + origin.x, visibleSize.height / 2 + origin.y, 0));
	car_cabine->setPositionX(0);
	car_cabine->setPositionY(10);
	car_cabine->setPositionZ(0);
	car_cabine->setGlobalZOrder(-1);
	car_cabine->addComponent(cabine_component);
	cabine_component->syncNodeToPhysics();
	cabine_component->setSyncFlag(Physics3DComponent::PhysicsSyncFlag::PHYSICS_TO_NODE);
	car_cabine->setCameraMask((unsigned short)CameraFlag::USER2);
	this->addChild(car_cabine, 1);


	Physics3DRigidBodyDes rbd_wheel1;
	rbd_wheel1.disableSleep = true;
	rbd_wheel1.mass = 15.0f;
	//rbd_wheel1.shape = Physics3DShape::createCylinder(3, 1);
	rbd_wheel1.shape = Physics3DShape::createSphere(1.5);
	auto physics_rbd_wheel1 = Physics3DRigidBody::create(&rbd_wheel1);
	auto wheel1_component = Physics3DComponent::create(physics_rbd_wheel1);
	wheel1 = Sprite3D::create();
	//wheel1->setTexture("Gun.png");
	//wheel1->setPosition3D(Vec3(visibleSize.width / 2 + origin.x, visibleSize.height / 2 + origin.y, 0));
	wheel1->setPositionX(0);
	wheel1->setPositionY(10);
	wheel1->setPositionZ(0);
	wheel1->setGlobalZOrder(-1);
	wheel1->addComponent(wheel1_component);
	wheel1_component->syncNodeToPhysics();
	wheel1_component->setSyncFlag(Physics3DComponent::PhysicsSyncFlag::PHYSICS_TO_NODE);
	wheel1->setCameraMask((unsigned short)CameraFlag::USER2);
	this->addChild(wheel1, 1);

	Physics3DRigidBodyDes rbd_wheel2;
	rbd_wheel2.disableSleep = true;
	rbd_wheel2.mass = 15.0f;
	//rbd_wheel2.shape = Physics3DShape::createCylinder(3, 1);
	rbd_wheel2.shape = Physics3DShape::createSphere(1.5);
	auto physics_rbd_wheel2 = Physics3DRigidBody::create(&rbd_wheel2);
	auto wheel2_component = Physics3DComponent::create(physics_rbd_wheel2);
	wheel2 = Sprite3D::create();
	//wheel2->setTexture("Gun.png");
	//wheel2->setPosition3D(Vec3(visibleSize.width / 2 + origin.x, visibleSize.height / 2 + origin.y, 0));
	wheel2->setPositionX(0);
	wheel2->setPositionY(10);
	wheel2->setPositionZ(0);
	wheel2->setGlobalZOrder(-1);
	wheel2->addComponent(wheel2_component);
	wheel2_component->syncNodeToPhysics();
	wheel2_component->setSyncFlag(Physics3DComponent::PhysicsSyncFlag::PHYSICS_TO_NODE);
	wheel2->setCameraMask((unsigned short)CameraFlag::USER2);
	this->addChild(wheel2, 1);

	Physics3DRigidBodyDes rbd_wheel3;
	rbd_wheel3.disableSleep = true;
	rbd_wheel3.mass = 15.0f;
	rbd_wheel3.shape = Physics3DShape::createSphere(1.5);
	//rbd_wheel3.shape = Physics3DShape::createCylinder(3, 1);
	physics_rbd_wheel3 = Physics3DRigidBody::create(&rbd_wheel3);
	auto wheel3_component = Physics3DComponent::create(physics_rbd_wheel3);
	wheel3 = Sprite3D::create();
	//wheel3->setTexture("Gun.png");
	//wheel3->setPosition3D(Vec3(visibleSize.width / 2 + origin.x, visibleSize.height / 2 + origin.y, 0));
	wheel3->setPositionX(0);
	wheel3->setPositionY(10);
	wheel3->setPositionZ(0);
	wheel3->setGlobalZOrder(-1);
	wheel3->addComponent(wheel3_component);
	wheel3_component->syncNodeToPhysics();
	wheel3_component->setSyncFlag(Physics3DComponent::PhysicsSyncFlag::PHYSICS_TO_NODE);
	wheel3->setCameraMask((unsigned short)CameraFlag::USER2);
	this->addChild(wheel3, 1);

	Physics3DRigidBodyDes rbd_wheel4;
	rbd_wheel4.disableSleep = true;
	rbd_wheel4.mass = 15.0f;
	//rbd_wheel4.shape = Physics3DShape::createCylinder(3, 1);
	rbd_wheel4.shape = Physics3DShape::createSphere(1.5);
	physics_rbd_wheel4 = Physics3DRigidBody::create(&rbd_wheel4);
	auto wheel4_component = Physics3DComponent::create(physics_rbd_wheel4);
	wheel4 = Sprite3D::create();
	//wheel3->setTexture("Gun.png");
	//wheel4->setPosition3D(Vec3(visibleSize.width / 2 + origin.x, visibleSize.height / 2 + origin.y, 0));
	wheel4->setPositionX(0);
	wheel4->setPositionY(10);
	wheel4->setPositionZ(0);
	wheel4->setGlobalZOrder(-1);
	wheel4->addComponent(wheel4_component);
	wheel4_component->syncNodeToPhysics();
	wheel4_component->setSyncFlag(Physics3DComponent::PhysicsSyncFlag::PHYSICS_TO_NODE);
	wheel4->setCameraMask((unsigned short)CameraFlag::USER2);
	this->addChild(wheel4, 1);

	Physics3DRigidBodyDes rbd_wheel_bar;
	rbd_wheel_bar.disableSleep = true;
	rbd_wheel_bar.mass = 400.0f;
	rbd_wheel_bar.shape = Physics3DShape::createBox(Vec3(2, 2.0f, 6));
	physics_rbd_wheel_bar = Physics3DRigidBody::create(&rbd_wheel_bar);
	auto car_wheel_bar_component = Physics3DComponent::create(physics_rbd_wheel_bar);
	car_wheel_bar = Sprite3D::create();
	//car_wheel_bar->setTexture("Gun.png");
	car_wheel_bar->setScale(0.01);
	car_wheel_bar->setPositionX(0);
	car_wheel_bar->setPositionY(10);
	car_wheel_bar->setPositionZ(0);
	//car_wheel_bar->setPosition3D(Vec3(visibleSize.width / 2 + origin.x, visibleSize.height / 2 + origin.y, 0));
	car_wheel_bar->setGlobalZOrder(-1);
	car_wheel_bar->addComponent(car_wheel_bar_component);
	car_wheel_bar_component->syncNodeToPhysics();
	car_wheel_bar_component->setSyncFlag(Physics3DComponent::PhysicsSyncFlag::PHYSICS_TO_NODE);
	car_wheel_bar->setCameraMask((unsigned short)CameraFlag::USER2);
	this->addChild(car_wheel_bar, 1);

	auto constraint = Physics3DHingeConstraint::create(physics_rbd_cabine, physics_rbd_wheel1, Vec3(0.0f, 0.0, 6.0f), Vec3(0.f, -3.0f, 0.f), Vec3(1.0f, 0.0, 0.0f), Vec3(0.f, 1.0f, 0.f));
	psychics_scene->getPhysics3DWorld()->addPhysics3DConstraint(constraint);
	auto constraint2 = Physics3DHingeConstraint::create(physics_rbd_cabine, physics_rbd_wheel2, Vec3(0.0f, 0.0, 6.0f), Vec3(0.f, 3.0f, 0.f), Vec3(1.0f, 0.0, 0.0f), Vec3(0.f, 1.0f, 0.f));
	psychics_scene->getPhysics3DWorld()->addPhysics3DConstraint(constraint2);


	constraint5 = Physics3DHingeConstraint::create(physics_rbd_cabine, physics_rbd_wheel_bar, Vec3(0.0f, 0.0, -6.0f), Vec3(0.f, 0.0f, 0.f), Vec3(0.0f, 1.0, 0.0f), Vec3(0.f, 1.0f, 0.f));
	constraint5->setLimit(CC_DEGREES_TO_RADIANS(-0.5), CC_DEGREES_TO_RADIANS(0.5));
	psychics_scene->getPhysics3DWorld()->addPhysics3DConstraint(constraint5);


	auto constraint3 = Physics3DHingeConstraint::create(physics_rbd_wheel_bar, physics_rbd_wheel3, Vec3(0.0f, 0.0, -3.0f), Vec3(0.f, -3.0f, 0.f), Vec3(1.0f, 0.0, 0.0f), Vec3(0.f, 1.0f, 0.f));
	psychics_scene->getPhysics3DWorld()->addPhysics3DConstraint(constraint3);
	auto constraint4 = Physics3DHingeConstraint::create(physics_rbd_wheel_bar, physics_rbd_wheel4, Vec3(0.0f, 0.0, -3.0f), Vec3(0.f, 3.0f, 0.f), Vec3(1.0f, 0.0, 0.0f), Vec3(0.f, 1.0f, 0.f));
	psychics_scene->getPhysics3DWorld()->addPhysics3DConstraint(constraint4);


	//add a point light
	auto light = PointLight::create(Vec3(0, 50, 0), Color3B(255, 255, 255), 150);
	addChild(light);
	//set the ambient light 
	auto ambient = AmbientLight::create(Color3B(55, 55, 55));
	addChild(ambient);


	/**
	Physics3DRigidBodyDes boxesrbDes;
	boxesrbDes.mass = 1.f;
	boxesrbDes.shape = Physics3DShape::createBox(Vec3(5, 5, 5));
	float start_x = 10- ARRAY_SIZE_X / 2;
	float start_y = 0 + 5.0f;
	float start_z = 10 - ARRAY_SIZE_Z / 2;

	for (int k = 0; k<ARRAY_SIZE_Y; k++)
	{
		for (int i = 0; i<ARRAY_SIZE_X; i++)
		{
			for (int j = 0; j<ARRAY_SIZE_Z; j++)
			{
				float x = 1.0*i + start_x;
				float y = 5.0 + 1.0*k + start_y;
				float z = 1.0*j + start_z;
				boxesrbDes.originalTransform.setIdentity();
				boxesrbDes.originalTransform.translate(x, y, z);

				auto sprite = PhysicsSprite3D::create("box.c3t", &boxesrbDes);
				sprite->setTexture("plane.png");
				sprite->setCameraMask((unsigned short)CameraFlag::USER1);
				sprite->setScale(1.0f / sprite->getContentSize().width);
				this->addChild(sprite);
				sprite->setPosition3D(Vec3(x, y, z));
				sprite->syncNodeToPhysics();

				sprite->setSyncFlag(Physics3DComponent::PhysicsSyncFlag::PHYSICS_TO_NODE);
			}
		}
	}

	**/



	auto listener = EventListenerTouchAllAtOnce::create();
	listener->onTouchesBegan = CC_CALLBACK_2(HelloWorld::onTouchesBegan, this);
	listener->onTouchesMoved = CC_CALLBACK_2(HelloWorld::onTouchesMoved, this);
	listener->onTouchesEnded = CC_CALLBACK_2(HelloWorld::onTouchesEnded, this);
	_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);


	//psychics_scene->setPhysics3DDebugCamera(camera); 
	schedule(schedule_selector(HelloWorld::myupdate), .005);
	//this->scheduleUpdate();
	
    return true;
}
bool rcBuildPolyMeshDetail(const rcPolyMesh& mesh, const rcCompactHeightfield& chf,
						   const float sampleDist, const float sampleMaxError,
						   rcPolyMeshDetail& dmesh)
{
	rcTimeVal startTime = rcGetPerformanceTimer();
	
	if (mesh.nverts == 0 || mesh.npolys == 0)
		return true;
	
	const int nvp = mesh.nvp;
	const float cs = mesh.cs;
	const float ch = mesh.ch;
	const float* orig = mesh.bmin;
	
	rcIntArray edges(64);
	rcIntArray tris(512);
	rcIntArray idx(512);
	rcIntArray stack(512);
	rcIntArray samples(512);
	float verts[256*3];
	float* poly = 0;
	int* bounds = 0;
	rcHeightPatch hp;
	int nPolyVerts = 0;
	int maxhw = 0, maxhh = 0;
	
	bounds = new int[mesh.npolys*4];
	if (!bounds)
	{
		if (rcGetLog())
			rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'bounds' (%d).", mesh.npolys*4);
		goto failure;
	}
	poly = new float[nvp*3];
	if (!bounds)
	{
		if (rcGetLog())
			rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'poly' (%d).", nvp*3);
		goto failure;
	}
	
	// Find max size for a polygon area.
	for (int i = 0; i < mesh.npolys; ++i)
	{
		const unsigned short* p = &mesh.polys[i*nvp*2];
		int& xmin = bounds[i*4+0];
		int& xmax = bounds[i*4+1];
		int& ymin = bounds[i*4+2];
		int& ymax = bounds[i*4+3];
		xmin = chf.width;
		xmax = 0;
		ymin = chf.height;
		ymax = 0;
		for (int j = 0; j < nvp; ++j)
		{
			if(p[j] == 0xffff) break;
			const unsigned short* v = &mesh.verts[p[j]*3];
			xmin = rcMin(xmin, (int)v[0]);
			xmax = rcMax(xmax, (int)v[0]);
			ymin = rcMin(ymin, (int)v[2]);
			ymax = rcMax(ymax, (int)v[2]);
			nPolyVerts++;
		}
		xmin = rcMax(0,xmin-1);
		xmax = rcMin(chf.width,xmax+1);
		ymin = rcMax(0,ymin-1);
		ymax = rcMin(chf.height,ymax+1);
		if (xmin >= xmax || ymin >= ymax) continue;
		maxhw = rcMax(maxhw, xmax-xmin);
		maxhh = rcMax(maxhh, ymax-ymin);
	}
	
	hp.data = new unsigned short[maxhw*maxhh];
	if (!hp.data)
	{
		if (rcGetLog())
			rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'hp.data' (%d).", maxhw*maxhh);
		goto failure;
	}
		
	dmesh.nmeshes = mesh.npolys;
	dmesh.nverts = 0;
	dmesh.ntris = 0;
	dmesh.meshes = new unsigned short[dmesh.nmeshes*4];
	if (!dmesh.meshes)
	{
		if (rcGetLog())
			rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.meshes' (%d).", dmesh.nmeshes*4);
		goto failure;
	}

	int vcap = nPolyVerts+nPolyVerts/2;
	int tcap = vcap*2;

	dmesh.nverts = 0;
	dmesh.verts = new float[vcap*3];
	if (!dmesh.verts)
	{
		if (rcGetLog())
			rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.verts' (%d).", vcap*3);
		goto failure;
	}
	dmesh.ntris = 0;
	dmesh.tris = new unsigned char[tcap*4];
	if (!dmesh.tris)
	{
		if (rcGetLog())
			rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' (%d).", tcap*4);
		goto failure;
	}
	
	for (int i = 0; i < mesh.npolys; ++i)
	{
		const unsigned short* p = &mesh.polys[i*nvp*2];
		
		// Find polygon bounding box.
		int npoly = 0;
		for (int j = 0; j < nvp; ++j)
		{
			if(p[j] == 0xffff) break;
			const unsigned short* v = &mesh.verts[p[j]*3];
			poly[j*3+0] = orig[0] + v[0]*cs;
			poly[j*3+1] = orig[1] + v[1]*ch;
			poly[j*3+2] = orig[2] + v[2]*cs;
			npoly++;
		}
		
		// Get the height data from the area of the polygon.
		hp.xmin = bounds[i*4+0];
		hp.ymin = bounds[i*4+2];
		hp.width = bounds[i*4+1]-bounds[i*4+0];
		hp.height = bounds[i*4+3]-bounds[i*4+2];
		getHeightData(chf, p, npoly, mesh.verts, hp, stack);
		
		// Build detail mesh.
		int nverts = 0;
		if (!buildPolyDetail(poly, npoly, mesh.regs[i],
							 sampleDist, sampleMaxError,
							 chf, hp, verts, nverts, tris,
							 edges, idx, samples))
		{
			goto failure;
		}

		// Offset detail vertices, unnecassary?
		for (int j = 0; j < nverts; ++j)
			verts[j*3+1] += chf.ch;
	
		// Store detail submesh.
		const int ntris = tris.size()/4;
		
		dmesh.meshes[i*4+0] = dmesh.nverts;
		dmesh.meshes[i*4+1] = (unsigned short)nverts;
		dmesh.meshes[i*4+2] = dmesh.ntris;
		dmesh.meshes[i*4+3] = (unsigned short)ntris;
		
		// Store vertices, allocate more memory if necessary.
		if (dmesh.nverts+nverts > vcap)
		{
			while (dmesh.nverts+nverts > vcap)
				vcap += 256;
				
			float* newv = new float[vcap*3];
			if (!newv)
			{
				if (rcGetLog())
					rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newv' (%d).", vcap*3);
				goto failure;
			}
			if (dmesh.nverts)
				memcpy(newv, dmesh.verts, sizeof(float)*3*dmesh.nverts);
			delete [] dmesh.verts;
			dmesh.verts = newv;
		}
		for (int j = 0; j < nverts; ++j)
		{
			dmesh.verts[dmesh.nverts*3+0] = verts[j*3+0];
			dmesh.verts[dmesh.nverts*3+1] = verts[j*3+1];
			dmesh.verts[dmesh.nverts*3+2] = verts[j*3+2];
			dmesh.nverts++;
		}
		
		// Store triangles, allocate more memory if necessary.
		if (dmesh.ntris+ntris > tcap)
		{
			while (dmesh.ntris+ntris > tcap)
				tcap += 256;
			unsigned char* newt = new unsigned char[tcap*4];
			if (!newt)
			{
				if (rcGetLog())
					rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newt' (%d).", tcap*4);
				goto failure;
			}
			if (dmesh.ntris)
				memcpy(newt, dmesh.tris, sizeof(unsigned char)*4*dmesh.ntris);
			delete [] dmesh.tris;
			dmesh.tris = newt;
		}
		for (int j = 0; j < ntris; ++j)
		{
			const int* t = &tris[j*4];
			dmesh.tris[dmesh.ntris*4+0] = (unsigned char)t[0];
			dmesh.tris[dmesh.ntris*4+1] = (unsigned char)t[1];
			dmesh.tris[dmesh.ntris*4+2] = (unsigned char)t[2];
			dmesh.tris[dmesh.ntris*4+3] = getTriFlags(&verts[t[0]*3], &verts[t[1]*3], &verts[t[2]*3], poly, npoly);
			dmesh.ntris++;
		}
	}
	
	delete [] bounds;
	delete [] poly;
	
	rcTimeVal endTime = rcGetPerformanceTimer();
	
	if (rcGetBuildTimes())
		rcGetBuildTimes()->buildDetailMesh += rcGetDeltaTimeUsec(startTime, endTime);

	return true;

failure:

	delete [] bounds;
	delete [] poly;

	return false;
}
示例#8
0
bool Physics3DTerrainDemo::init()
{
    if (!Physics3DTestDemo::init())
        return false;

    Terrain::DetailMap r("TerrainTest/dirt.jpg"),g("TerrainTest/Grass2.jpg",10),b("TerrainTest/road.jpg"),a("TerrainTest/GreenSkin.jpg",20);
    
    Terrain::TerrainData data("TerrainTest/heightmap129.jpg","TerrainTest/alphamap.png",r,g,b,a,Size(32,32), 20.0f, 1.0f);
    auto terrain = Terrain::create(data,Terrain::CrackFixedType::SKIRT);
    terrain->setMaxDetailMapAmount(4);
    terrain->setCameraMask(2);
    terrain->setDrawWire(false);
    
    terrain->setSkirtHeightRatio(3);
    terrain->setLODDistance(64,128,192);
    terrain->setCameraMask((unsigned short)CameraFlag::USER1);

    //create terrain
    Physics3DRigidBodyDes rbDes;
    rbDes.mass = 0.0f;
    std::vector<float> heidata = terrain->getHeightData();
    auto size = terrain->getTerrainSize();
    rbDes.shape = Physics3DShape::createHeightfield(size.width, size.height, &heidata[0], 1.0f, terrain->getMinHeight(), terrain->getMaxHeight(), true, false, true);
    auto rigidBody = Physics3DRigidBody::create(&rbDes);
    auto component = Physics3DComponent::create(rigidBody);
    terrain->addComponent(component);
    this->addChild(terrain);
    component->syncNodeToPhysics();
    component->setSyncFlag(Physics3DComponent::PhysicsSyncFlag::NONE);


    //create several spheres
    rbDes.mass = 1.f;
    rbDes.shape = Physics3DShape::createSphere(0.5f);
    float start_x = START_POS_X - ARRAY_SIZE_X/2 + 5.0f;
    float start_y = START_POS_Y + 20.0f;
    float start_z = START_POS_Z - ARRAY_SIZE_Z/2;

    for (int k=0;k<ARRAY_SIZE_Y;k++)
    {
        for (int i=0;i<ARRAY_SIZE_X;i++)
        {
            for(int j = 0;j<ARRAY_SIZE_Z;j++)
            {
                float x = 1.0*i + start_x;
                float y = 5.0+1.0*k + start_y;
                float z = 1.0*j + start_z;

                auto sprite = PhysicsSprite3D::create("Sprite3DTest/sphere.c3b", &rbDes);
                sprite->setTexture("Sprite3DTest/plane.png");
                sprite->setCameraMask((unsigned short)CameraFlag::USER1);
                sprite->setScale(1.0f / sprite->getContentSize().width);
                sprite->setPosition3D(Vec3(x, y, z));
                this->addChild(sprite);
                sprite->syncNodeToPhysics();
                sprite->setSyncFlag(Physics3DComponent::PhysicsSyncFlag::PHYSICS_TO_NODE);
            }
        }
    }

    //create mesh
    std::vector<Vec3> trianglesList = Bundle3D::getTrianglesList("Sprite3DTest/boss.c3b");

    rbDes.mass = 0.0f;
    rbDes.shape = Physics3DShape::createMesh(&trianglesList[0], (int)trianglesList.size() / 3);
    rigidBody = Physics3DRigidBody::create(&rbDes);
    component = Physics3DComponent::create(rigidBody);
    auto sprite = Sprite3D::create("Sprite3DTest/boss.c3b");
    sprite->addComponent(component);
    sprite->setRotation3D(Vec3(-90.0f, 0.0f, 0.0f));
    sprite->setPosition3D(Vec3(0.0f, 15.0f, 0.0f));
    sprite->setCameraMask(2);
    this->addChild(sprite);

    std::vector<std::pair<Physics3DShape*, Mat4> > shapeList;
    {
        Mat4 localTrans;
        auto bodyshape = Physics3DShape::createBox(Vec3(2.0f, 4.0f, 2.0f));
        Mat4::createTranslation(0.0f, 2.0f, 0.0f, &localTrans);
        shapeList.push_back(std::make_pair(bodyshape, localTrans));
        auto headshape = Physics3DShape::createSphere(1.5f);
        Mat4::createTranslation(0.6f, 5.0f, -1.5f, &localTrans);
        shapeList.push_back(std::make_pair(headshape, localTrans));
        auto lhandshape = Physics3DShape::createBox(Vec3(1.0f, 3.0f, 1.0f));
        Mat4::createRotation(Vec3(1.0f, 0.0f, 0.0f), CC_DEGREES_TO_RADIANS(15.0f), &localTrans);
        localTrans.m[12] = -1.5f; localTrans.m[13] = 2.5f; localTrans.m[14] = -2.5f;
        shapeList.push_back(std::make_pair(lhandshape, localTrans));
        auto rhandshape = Physics3DShape::createBox(Vec3(1.0f, 3.0f, 1.0f));
        Mat4::createRotation(Vec3(1.0f, 0.0f, 0.0f), CC_DEGREES_TO_RADIANS(-15.0f), &localTrans);
        localTrans.m[12] = 2.0f; localTrans.m[13] = 2.5f; localTrans.m[14] = 1.f;
        shapeList.push_back(std::make_pair(rhandshape, localTrans));

        rbDes.mass = 10.0f;
        rbDes.shape = Physics3DShape::createCompoundShape(shapeList);
        rigidBody = Physics3DRigidBody::create(&rbDes);
        component = Physics3DComponent::create(rigidBody);
        auto sprite = Sprite3D::create("Sprite3DTest/orc.c3b");
        sprite->addComponent(component);
        sprite->setRotation3D(Vec3(0.0f, 180.0f, 0.0f));
        sprite->setPosition3D(Vec3(-5.0f, 20.0f, 0.0f));
        sprite->setScale(0.4f);
        sprite->setCameraMask(2);
        this->addChild(sprite);
    }


    physicsScene->setPhysics3DDebugCamera(_camera);
    return true;
}
示例#9
0
	/// \brief Drawing the DynamicTerrain
	Action::ResultE DynamicTerrain::drawPrimitives( DrawActionBase* action )
	{
		// do frustum culling here.. extract frustum from the current camera:
		RenderAction* renderAction = dynamic_cast< RenderAction* >( action );

		if( needInitialize_ )
		{
			if( getHeightData() != NullFC )
			{
				if( getLevelSize() < 3 )
				{
					SWARNING << "DynamicTerrain: LevelSize is below minimum (using default)!" << std::endl;

					setLevelSize( 63 );
				}

				// todo: choose the correct height-/texturedata source:
				geoClipmaps_.initialize( getLevelSize(), &imageHeightSource_, getTextureSource() );
			}
			needInitialize_ = false;
		}

		if( !geoClipmaps_.isValid() )
		{
			// no valid data yet
			return Action::Continue;
		}

		// todo: get the viewport of the RenderAction, check if the camera already has a terrain attachment:
			// if not: create a new TerrainView and attach it to the camera

		// update/render the view
		if( renderAction )
		{
			// frustum culling
			const FrustumVolume& frustum = renderAction->getFrustum();

			// make an update right here:
			Matrix camera  = renderAction->getCameraToWorld();
			Matrix toworld = renderAction->top_matrix();			
			toworld.invert();
			camera.multLeft(toworld);
			Pnt3f eyePoint( camera[ 3 ][ 0 ], camera[ 3 ][ 1 ], camera[ 3 ][ 2 ] );

			// transform the eyePoint to the unscaled sample space:
			const WorldTransformation worldTransform = getWorldTransform();
			const Pnt3f worldOffset( worldTransform.offset[ 0 ], 0.0f, worldTransform.offset[ 1 ] );
			
			const Pnt3f localEyePoint = componentDivide( ( eyePoint - worldOffset ), worldTransform.sampleDistance );

			if( !getDisableUpdate() )
			{
				geoClipmaps_.update( localEyePoint );
			}			
			
			// and now draw what we have:
			ClipmapRenderParameters renderParams;
			
			renderParams.renderAction			= renderAction;
			renderParams.window					= renderAction->getWindow();
			renderParams.viewFrustum			= frustum;
			renderParams.enableFrustumCulling	= getEnableFrustumCulling();
			renderParams.showTransitionRegions	= getShowTransitionRegions();
			renderParams.useVboExtension		= getUseVboExtension();
			renderParams.globalTexture			= globalTexture_;
			renderParams.heightColorTexture		= getHeightColorTexture();
			
			renderParams.worldTransform			= worldTransform;

			ClipmapRenderStatistics renderStats;

			geoClipmaps_.render( renderParams, renderStats );

			if( getShowBoundingBoxes() )
			{
				//drawBox( 
			}

			// update stats:
			StatCollector* statCollector = action->getStatistics();
			
			if( statCollector ) 
			{
				StatIntElem* statTriangleCount = statCollector->getElem( Drawable::statNTriangles, false );
				StatIntElem* statVertexCount = statCollector->getElem( Drawable::statNVertices, false );

				if( statTriangleCount )
				{
					statTriangleCount->add( renderStats.drawnTriangleCount );
				}
				if( statVertexCount )
				{
					statVertexCount->add( renderStats.transformedVertexCount );
				}
			}
		}
		else
		{
			//todo: can this ever happen?!
			SLOG << "Test\n";
		}

		return Action::Continue;
	}
示例#10
0
	void DynamicTerrain::changed( BitVector whichField, UInt32 origin )
	{
		Inherited::changed( whichField, origin );

		// todo: prevent multiple initializations: (how?)
		if( ( whichField & DynamicTerrain::HeightDataFieldMask ) )
		{
			// heightdata image changed: use the image as data source and reinitialize the data structures:
			imageHeightSource_.setImage( getHeightData() );

			std::cerr << "HeightDataField changed" << std::endl;

			needInitialize_ = true;
		}
		if( ( whichField & DynamicTerrain::TextureDataFieldMask ) )
		{
			imageTextureSource_.setImage( getTextureData() );

			needInitialize_ = true;
		}
		if( ( whichField & DynamicTerrain::LevelSizeFieldMask ) )
		{
			// the level size has changed: we need to reinitialize:
			needInitialize_ = true;			
		}    
		if( whichField & DynamicTerrain::UseGpuRendererFieldMask )
		{
			// todo: change the used renderer
		}
		if( ( whichField & DynamicTerrain::HeightDataOffsetFieldMask ) ||
			( whichField & DynamicTerrain::HeightDataScaleFieldMask ) )
		{
			invalidateVolume();
		}
		if( whichField & DynamicTerrain::CpuVertexProgramFieldId )
		{
			geoClipmaps_.setCpuVertexProgramText( getCpuVertexProgram() );
		}	
		if( whichField & DynamicTerrain::CpuFragmentProgramFieldId )
		{
			geoClipmaps_.setCpuFragmentProgramText( getCpuFragmentProgram() );			
		}
		if( whichField & DynamicTerrain::TextureDataFieldId )
		{
			// create a texture chunk:
			ImagePtr textureImage = getTextureData();

			if( textureImage != NullFC )
			{
				globalTexture_ = TextureChunk::create();

				beginEditCP( globalTexture_ );

				globalTexture_->setImage( textureImage );
				globalTexture_->setWrapS( GL_CLAMP );
				globalTexture_->setWrapT( GL_CLAMP );
				globalTexture_->setWrapR( GL_CLAMP );

				globalTexture_->setMinFilter( GL_LINEAR );
				globalTexture_->setMagFilter( GL_LINEAR );
				globalTexture_->setEnvMode( GL_MODULATE );			

				endEditCP( globalTexture_ );
			}
			else
			{
				globalTexture_ = NullFC;
			}
		}
	}