mirror of
https://github.com/AxioDL/metaforce.git
synced 2025-12-08 17:44:56 +00:00
SIMD refactor
This commit is contained in:
@@ -52,7 +52,7 @@ void ANIM::IANIM::sendANIMToBlender(hecl::blender::PyOutStream& os, const DNAANI
|
||||
{
|
||||
size_t idx = 0;
|
||||
for (const DNAANIM::Value& val : rotKeys)
|
||||
fixedRotKeys[idx++][c] = val.v4.vec[c];
|
||||
fixedRotKeys[idx++][c] = val.simd[c];
|
||||
}
|
||||
|
||||
for (zeus::CQuaternion& rot : fixedRotKeys)
|
||||
@@ -77,7 +77,7 @@ void ANIM::IANIM::sendANIMToBlender(hecl::blender::PyOutStream& os, const DNAANI
|
||||
{
|
||||
size_t idx = 0;
|
||||
for (const DNAANIM::Value& val : transKeys)
|
||||
fixedTransKeys[idx++][c] = val.v3.vec[c];
|
||||
fixedTransKeys[idx++][c] = val.simd[c];
|
||||
}
|
||||
|
||||
for (zeus::CVector3f& t : fixedTransKeys)
|
||||
@@ -281,7 +281,7 @@ void ANIM::ANIM0::Enumerate<BigDNA::Write>(athena::io::IStreamWriter& writer)
|
||||
const std::vector<DNAANIM::Value>& keys = *cit++;
|
||||
auto kit = keys.begin();
|
||||
for (size_t k=0 ; k<head.keyCount ; ++k)
|
||||
writer.writeVec4fBig((*kit++).v4);
|
||||
writer.writeVec4fBig(atVec4f{(*kit++).simd});
|
||||
if (bone.second)
|
||||
{
|
||||
transKeyCount += head.keyCount;
|
||||
@@ -299,7 +299,7 @@ void ANIM::ANIM0::Enumerate<BigDNA::Write>(athena::io::IStreamWriter& writer)
|
||||
const std::vector<DNAANIM::Value>& keys = *cit++;
|
||||
auto kit = keys.begin();
|
||||
for (size_t k=0 ; k<head.keyCount ; ++k)
|
||||
writer.writeVec3fBig((*kit++).v3);
|
||||
writer.writeVec3fBig(atVec3f{(*kit++).simd});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -655,9 +655,9 @@ ANIM::ANIM(const BlenderAction& act,
|
||||
zeus::CQuaternion q(key.rotation.val);
|
||||
q = rig.restoreRotation(newChan.id, q);
|
||||
if (sign == 0.f)
|
||||
sign = q.w < 0.f ? -1.f : 1.f;
|
||||
sign = q.w() < 0.f ? -1.f : 1.f;
|
||||
q *= sign;
|
||||
rotVals.emplace_back(q.w, q.x, q.y, q.z);
|
||||
rotVals.emplace_back(q.mSimd);
|
||||
}
|
||||
|
||||
if (chan.attrMask & 0x2)
|
||||
@@ -674,7 +674,7 @@ ANIM::ANIM(const BlenderAction& act,
|
||||
{
|
||||
zeus::CVector3f pos(key.position.val);
|
||||
pos = rig.restorePosition(newChan.id, pos, true);
|
||||
transVals.emplace_back(pos.x, pos.y, pos.z);
|
||||
transVals.emplace_back(pos.mSimd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,15 +64,19 @@ void CINF::sendCINFToBlender(hecl::blender::PyOutStream& os, const UniqueID32& c
|
||||
cinfId.toUint32());
|
||||
|
||||
for (const DNAANIM::RigInverter<CINF>::Bone& bone : inverter.getBones())
|
||||
{
|
||||
zeus::simd_floats originF(bone.m_origBone.origin.simd);
|
||||
zeus::simd_floats tailF(bone.m_tail.mSimd);
|
||||
os.format("bone = arm.edit_bones.new('%s')\n"
|
||||
"bone.head = (%f,%f,%f)\n"
|
||||
"bone.tail = (%f,%f,%f)\n"
|
||||
"bone.use_inherit_scale = False\n"
|
||||
"arm_bone_table[%u] = bone\n",
|
||||
getBoneNameFromId(bone.m_origBone.id)->c_str(),
|
||||
bone.m_origBone.origin.vec[0], bone.m_origBone.origin.vec[1], bone.m_origBone.origin.vec[2],
|
||||
bone.m_tail[0], bone.m_tail[1], bone.m_tail[2],
|
||||
originF[0], originF[1], originF[2],
|
||||
tailF[0], tailF[1], tailF[2],
|
||||
bone.m_origBone.id);
|
||||
}
|
||||
|
||||
for (const Bone& bone : bones)
|
||||
if (bone.parentId != 2)
|
||||
|
||||
@@ -1304,38 +1304,38 @@ MaterialSet::Material::UVAnimation::UVAnimation(const std::string& gameFunction,
|
||||
mode = Mode::Scroll;
|
||||
if (gameArgs.size() < 2)
|
||||
Log.report(logvisor::Fatal, "Mode2 UV anim requires 2 vector arguments");
|
||||
vals[0] = gameArgs[0].vec[0];
|
||||
vals[1] = gameArgs[0].vec[1];
|
||||
vals[2] = gameArgs[1].vec[0];
|
||||
vals[3] = gameArgs[1].vec[1];
|
||||
vals[0] = gameArgs[0].simd[0];
|
||||
vals[1] = gameArgs[0].simd[1];
|
||||
vals[2] = gameArgs[1].simd[0];
|
||||
vals[3] = gameArgs[1].simd[1];
|
||||
}
|
||||
else if (!gameFunction.compare("RetroUVMode3Node"))
|
||||
{
|
||||
mode = Mode::Rotation;
|
||||
if (gameArgs.size() < 2)
|
||||
Log.report(logvisor::Fatal, "Mode3 UV anim requires 2 arguments");
|
||||
vals[0] = gameArgs[0].vec[0];
|
||||
vals[1] = gameArgs[1].vec[0];
|
||||
vals[0] = gameArgs[0].simd[0];
|
||||
vals[1] = gameArgs[1].simd[0];
|
||||
}
|
||||
else if (!gameFunction.compare("RetroUVMode4Node"))
|
||||
{
|
||||
mode = Mode::HStrip;
|
||||
if (gameArgs.size() < 4)
|
||||
Log.report(logvisor::Fatal, "Mode4 UV anim requires 4 arguments");
|
||||
vals[0] = gameArgs[0].vec[0];
|
||||
vals[1] = gameArgs[1].vec[0];
|
||||
vals[2] = gameArgs[2].vec[0];
|
||||
vals[3] = gameArgs[3].vec[0];
|
||||
vals[0] = gameArgs[0].simd[0];
|
||||
vals[1] = gameArgs[1].simd[0];
|
||||
vals[2] = gameArgs[2].simd[0];
|
||||
vals[3] = gameArgs[3].simd[0];
|
||||
}
|
||||
else if (!gameFunction.compare("RetroUVMode5Node"))
|
||||
{
|
||||
mode = Mode::VStrip;
|
||||
if (gameArgs.size() < 4)
|
||||
Log.report(logvisor::Fatal, "Mode5 UV anim requires 4 arguments");
|
||||
vals[0] = gameArgs[0].vec[0];
|
||||
vals[1] = gameArgs[1].vec[0];
|
||||
vals[2] = gameArgs[2].vec[0];
|
||||
vals[3] = gameArgs[3].vec[0];
|
||||
vals[0] = gameArgs[0].simd[0];
|
||||
vals[1] = gameArgs[1].simd[0];
|
||||
vals[2] = gameArgs[2].simd[0];
|
||||
vals[3] = gameArgs[3].simd[0];
|
||||
}
|
||||
else if (!gameFunction.compare("RetroUVMode6NodeN"))
|
||||
mode = Mode::Model;
|
||||
@@ -1344,8 +1344,8 @@ MaterialSet::Material::UVAnimation::UVAnimation(const std::string& gameFunction,
|
||||
mode = Mode::CylinderEnvironment;
|
||||
if (gameArgs.size() < 2)
|
||||
Log.report(logvisor::Fatal, "Mode7 UV anim requires 2 arguments");
|
||||
vals[0] = gameArgs[0].vec[0];
|
||||
vals[1] = gameArgs[1].vec[0];
|
||||
vals[0] = gameArgs[0].simd[0];
|
||||
vals[1] = gameArgs[1].simd[0];
|
||||
}
|
||||
else
|
||||
Log.report(logvisor::Fatal, "unsupported UV anim '%s'", gameFunction.c_str());
|
||||
|
||||
@@ -289,7 +289,7 @@ void PAKBridge::addPATHToMREA(PAKRouter<PAKBridge>& pakRouter,
|
||||
}
|
||||
}
|
||||
|
||||
static const atVec4f BottomRow = {0.f, 0.f, 0.f, 1.f};
|
||||
static const atVec4f BottomRow = {{0.f, 0.f, 0.f, 1.f}};
|
||||
|
||||
void PAKBridge::addMAPATransforms(PAKRouter<PAKBridge>& pakRouter,
|
||||
std::unordered_map<UniqueID32, zeus::CMatrix4f>& addTo,
|
||||
|
||||
@@ -366,15 +366,20 @@ bool FRME::Extract(const SpecBase &dataSpec,
|
||||
switch(info->type)
|
||||
{
|
||||
case LITEInfo::ELightType::LocalAmbient:
|
||||
{
|
||||
zeus::simd_floats colorF(w.header.color.simd);
|
||||
os.format("bg_node.inputs[0].default_value = (%f,%f,%f,1.0)\n"
|
||||
"bg_node.inputs[1].default_value = %f\n",
|
||||
w.header.color.vec[0], w.header.color.vec[1], w.header.color.vec[2],
|
||||
colorF[0], colorF[1], colorF[2],
|
||||
info->distQ / 8.0);
|
||||
break;
|
||||
}
|
||||
case LITEInfo::ELightType::Spot:
|
||||
case LITEInfo::ELightType::Directional:
|
||||
os << "angle = Quaternion((1.0, 0.0, 0.0), math.radians(90.0))\n";
|
||||
default:
|
||||
{
|
||||
zeus::simd_floats colorF(w.header.color.simd);
|
||||
os.format("lamp = bpy.data.lamps.new(name='%s', type='POINT')\n"
|
||||
"lamp.color = (%f, %f, %f)\n"
|
||||
"lamp.falloff_type = 'INVERSE_COEFFICIENTS'\n"
|
||||
@@ -387,7 +392,7 @@ bool FRME::Extract(const SpecBase &dataSpec,
|
||||
"lamp.retro_light_index = %d\n"
|
||||
"binding = lamp\n",
|
||||
w.header.name.c_str(),
|
||||
w.header.color.vec[0], w.header.color.vec[1], w.header.color.vec[2],
|
||||
colorF[0], colorF[1], colorF[2],
|
||||
info->distC, info->distL, info->distQ,
|
||||
info->angC, info->angL, info->angQ, info->loadedIdx);
|
||||
if (info->type == LITEInfo::ELightType::Spot)
|
||||
@@ -397,6 +402,7 @@ bool FRME::Extract(const SpecBase &dataSpec,
|
||||
else if (info->type == LITEInfo::ELightType::Directional)
|
||||
os << "lamp.type = 'HEMI'\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (w.type == SBIG('IMGP'))
|
||||
@@ -456,10 +462,8 @@ bool FRME::Extract(const SpecBase &dataSpec,
|
||||
ti = 2;
|
||||
else
|
||||
ti = i;
|
||||
os.format("verts.append(bm.verts.new((%f,%f,%f)))\n",
|
||||
info->quadCoords[ti].vec[0],
|
||||
info->quadCoords[ti].vec[1],
|
||||
info->quadCoords[ti].vec[2]);
|
||||
zeus::simd_floats f(info->quadCoords[ti].simd);
|
||||
os.format("verts.append(bm.verts.new((%f,%f,%f)))\n", f[0], f[1], f[2]);
|
||||
}
|
||||
os << "bm.faces.new(verts)\n"
|
||||
"bm.loops.layers.uv.new('UV')\n"
|
||||
@@ -473,9 +477,8 @@ bool FRME::Extract(const SpecBase &dataSpec,
|
||||
ti = 2;
|
||||
else
|
||||
ti = i;
|
||||
os.format("bm.verts[%d].link_loops[0][bm.loops.layers.uv[0]].uv = (%f,%f)\n", i,
|
||||
info->uvCoords[ti].vec[0],
|
||||
info->uvCoords[ti].vec[1]);
|
||||
zeus::simd_floats f(info->uvCoords[ti].simd);
|
||||
os.format("bm.verts[%d].link_loops[0][bm.loops.layers.uv[0]].uv = (%f,%f)\n", i, f[0], f[1]);
|
||||
}
|
||||
os.format("binding = bpy.data.meshes.new('%s')\n"
|
||||
"bm.to_mesh(binding)\n"
|
||||
@@ -485,6 +488,7 @@ bool FRME::Extract(const SpecBase &dataSpec,
|
||||
}
|
||||
}
|
||||
|
||||
zeus::simd_floats colorF(w.header.color.simd);
|
||||
os.format("frme_obj = bpy.data.objects.new(name='%s', object_data=binding)\n"
|
||||
"frme_obj.pass_index = %d\n"
|
||||
"parentName = '%s'\n"
|
||||
@@ -507,7 +511,7 @@ bool FRME::Extract(const SpecBase &dataSpec,
|
||||
w.header.defaultVisible ? "True" : "False",
|
||||
w.header.defaultActive ? "True" : "False",
|
||||
w.header.cullFaces ? "True" : "False",
|
||||
w.header.color.vec[0], w.header.color.vec[1], w.header.color.vec[2], w.header.color.vec[3],
|
||||
colorF[0], colorF[1], colorF[2], colorF[3],
|
||||
w.header.modelDrawFlags,
|
||||
w.isWorker ? "True" : "False",
|
||||
w.workerId);
|
||||
@@ -541,12 +545,10 @@ bool FRME::Extract(const SpecBase &dataSpec,
|
||||
using PANEInfo = Widget::PANEInfo;
|
||||
if (PANEInfo* info = static_cast<PANEInfo*>(w.widgetInfo.get()))
|
||||
{
|
||||
zeus::simd_floats f(info->scaleCenter.simd);
|
||||
os.format("frme_obj.retro_pane_dimensions = (%f,%f)\n"
|
||||
"frme_obj.retro_pane_scale_center = (%f,%f,%f)\n",
|
||||
info->xDim, info->zDim,
|
||||
info->scaleCenter.vec[0],
|
||||
info->scaleCenter.vec[1],
|
||||
info->scaleCenter.vec[2]);
|
||||
info->xDim, info->zDim, f[0], f[1], f[2]);
|
||||
}
|
||||
}
|
||||
else if (w.type == SBIG('TXPN'))
|
||||
@@ -559,6 +561,10 @@ bool FRME::Extract(const SpecBase &dataSpec,
|
||||
if (frme.version >= 1)
|
||||
jpFontPath = pakRouter.getWorking(info->jpnFont, true);
|
||||
|
||||
zeus::simd_floats scaleF(info->scaleCenter.simd);
|
||||
zeus::simd_floats fillF(info->fillColor.simd);
|
||||
zeus::simd_floats outlineF(info->outlineColor.simd);
|
||||
zeus::simd_floats extentF(info->blockExtent.simd);
|
||||
os.format("frme_obj.retro_pane_dimensions = (%f,%f)\n"
|
||||
"frme_obj.retro_pane_scale_center = (%f,%f,%f)\n"
|
||||
"frme_obj.retro_textpane_font_path = '%s'\n"
|
||||
@@ -572,22 +578,13 @@ bool FRME::Extract(const SpecBase &dataSpec,
|
||||
"frme_obj.retro_textpane_hjustification = bpy.types.Object.retro_textpane_hjustification[1]['items'][%d][0]\n"
|
||||
"frme_obj.retro_textpane_vjustification = bpy.types.Object.retro_textpane_vjustification[1]['items'][%d][0]\n",
|
||||
info->xDim, info->zDim,
|
||||
info->scaleCenter.vec[0],
|
||||
info->scaleCenter.vec[1],
|
||||
info->scaleCenter.vec[2],
|
||||
scaleF[0], scaleF[1], scaleF[2],
|
||||
fontPath.getRelativePathUTF8().data(),
|
||||
info->wordWrap ? "True" : "False",
|
||||
info->horizontal ? "True" : "False",
|
||||
info->fillColor.vec[0],
|
||||
info->fillColor.vec[1],
|
||||
info->fillColor.vec[2],
|
||||
info->fillColor.vec[3],
|
||||
info->outlineColor.vec[0],
|
||||
info->outlineColor.vec[1],
|
||||
info->outlineColor.vec[2],
|
||||
info->outlineColor.vec[3],
|
||||
info->blockExtent.vec[0],
|
||||
info->blockExtent.vec[1],
|
||||
fillF[0], fillF[1], fillF[2], fillF[3],
|
||||
outlineF[0], outlineF[1], outlineF[2], outlineF[3],
|
||||
extentF[0], extentF[1],
|
||||
jpFontPath.getRelativePathUTF8().data(),
|
||||
info->jpnPointScale[0],
|
||||
info->jpnPointScale[1],
|
||||
@@ -652,6 +649,10 @@ bool FRME::Extract(const SpecBase &dataSpec,
|
||||
}
|
||||
}
|
||||
|
||||
zeus::simd_floats xfMtxF[3];
|
||||
for (int i = 0; i < 3; ++i)
|
||||
w.basis[i].simd.copy_to(xfMtxF[i]);
|
||||
zeus::simd_floats originF(w.origin.simd);
|
||||
os.format("mtx = Matrix(((%f,%f,%f,%f),(%f,%f,%f,%f),(%f,%f,%f,%f),(0.0,0.0,0.0,1.0)))\n"
|
||||
"mtxd = mtx.decompose()\n"
|
||||
"frme_obj.rotation_mode = 'QUATERNION'\n"
|
||||
@@ -659,9 +660,9 @@ bool FRME::Extract(const SpecBase &dataSpec,
|
||||
"frme_obj.rotation_quaternion = mtxd[1] * angle\n"
|
||||
"frme_obj.scale = mtxd[2]\n"
|
||||
"bpy.context.scene.objects.link(frme_obj)\n",
|
||||
w.basis[0].vec[0], w.basis[0].vec[1], w.basis[0].vec[2], w.origin.vec[0],
|
||||
w.basis[1].vec[0], w.basis[1].vec[1], w.basis[1].vec[2], w.origin.vec[1],
|
||||
w.basis[2].vec[0], w.basis[2].vec[1], w.basis[2].vec[2], w.origin.vec[2]);
|
||||
xfMtxF[0][0], xfMtxF[0][1], xfMtxF[0][2], originF[0],
|
||||
xfMtxF[1][0], xfMtxF[1][1], xfMtxF[1][2], originF[1],
|
||||
xfMtxF[2][0], xfMtxF[2][1], xfMtxF[2][2], originF[2]);
|
||||
}
|
||||
|
||||
os.centerView();
|
||||
|
||||
@@ -463,9 +463,9 @@ bool MREA::Cook(const hecl::ProjectPath& outPath,
|
||||
}
|
||||
else
|
||||
{
|
||||
head.localToWorldMtx[0].vec[0] = 1.f;
|
||||
head.localToWorldMtx[1].vec[1] = 1.f;
|
||||
head.localToWorldMtx[2].vec[2] = 1.f;
|
||||
head.localToWorldMtx[0].simd[0] = 1.f;
|
||||
head.localToWorldMtx[1].simd[1] = 1.f;
|
||||
head.localToWorldMtx[2].simd[2] = 1.f;
|
||||
}
|
||||
head.meshCount = meshes.size();
|
||||
head.geomSecIdx = 0;
|
||||
|
||||
@@ -64,8 +64,10 @@ void PATH::sendToBlender(hecl::blender::Connection& conn, std::string_view entry
|
||||
entryName.data());
|
||||
|
||||
for (const Node& n : nodes)
|
||||
os.format("bm.verts.new((%f,%f,%f))\n",
|
||||
n.position.vec[0], n.position.vec[1], n.position.vec[2]);
|
||||
{
|
||||
zeus::simd_floats f(n.position.simd);
|
||||
os.format("bm.verts.new((%f,%f,%f))\n", f[0], f[1], f[2]);
|
||||
}
|
||||
|
||||
os << "bm.verts.ensure_lookup_table()\n";
|
||||
|
||||
@@ -128,15 +130,18 @@ void PATH::sendToBlender(hecl::blender::Connection& conn, std::string_view entry
|
||||
if (xf)
|
||||
{
|
||||
const zeus::CMatrix4f& w = *xf;
|
||||
zeus::simd_floats xfMtxF[4];
|
||||
for (int i = 0; i < 4; ++i)
|
||||
w.m[i].mSimd.copy_to(xfMtxF[i]);
|
||||
os.format("mtx = Matrix(((%f,%f,%f,%f),(%f,%f,%f,%f),(%f,%f,%f,%f),(0.0,0.0,0.0,1.0)))\n"
|
||||
"mtxd = mtx.decompose()\n"
|
||||
"path_mesh_obj.rotation_mode = 'QUATERNION'\n"
|
||||
"path_mesh_obj.location = mtxd[0]\n"
|
||||
"path_mesh_obj.rotation_quaternion = mtxd[1]\n"
|
||||
"path_mesh_obj.scale = mtxd[2]\n",
|
||||
w.m[0][0], w.m[1][0], w.m[2][0], w.m[3][0],
|
||||
w.m[0][1], w.m[1][1], w.m[2][1], w.m[3][1],
|
||||
w.m[0][2], w.m[1][2], w.m[2][2], w.m[3][2]);
|
||||
xfMtxF[0][0], xfMtxF[1][0], xfMtxF[2][0], xfMtxF[3][0],
|
||||
xfMtxF[0][1], xfMtxF[1][1], xfMtxF[2][1], xfMtxF[3][1],
|
||||
xfMtxF[0][2], xfMtxF[1][2], xfMtxF[2][2], xfMtxF[3][2]);
|
||||
}
|
||||
|
||||
os.linkBackground("//!area.blend");
|
||||
|
||||
@@ -27,7 +27,7 @@ zeus::CAABox Actor::getVISIAABB(hecl::blender::Token& btok) const
|
||||
aabbOut = zeus::CAABox(aabb.first, aabb.second);
|
||||
}
|
||||
|
||||
if (aabbOut.min.x > aabbOut.max.x)
|
||||
if (aabbOut.min.x() > aabbOut.max.x())
|
||||
return {};
|
||||
|
||||
zeus::CTransform xf = ConvertEditorEulerToTransform4f(scale, orientation, location);
|
||||
|
||||
@@ -19,7 +19,7 @@ zeus::CAABox DoorArea::getVISIAABB(hecl::blender::Token& btok) const
|
||||
aabbOut = zeus::CAABox(aabb.first, aabb.second);
|
||||
}
|
||||
|
||||
if (aabbOut.min.x > aabbOut.max.x)
|
||||
if (aabbOut.min.x() > aabbOut.max.x())
|
||||
return {};
|
||||
|
||||
zeus::CTransform xf = ConvertEditorEulerToTransform4f(scale, orientation, location);
|
||||
|
||||
@@ -269,9 +269,10 @@ zeus::CTransform ConvertEditorEulerToTransform4f(const zeus::CVector3f& scale,
|
||||
const zeus::CVector3f& orientation,
|
||||
const zeus::CVector3f& position)
|
||||
{
|
||||
return zeus::CTransform::RotateZ(zeus::degToRad(orientation.z)) *
|
||||
zeus::CTransform::RotateY(zeus::degToRad(orientation.y)) *
|
||||
zeus::CTransform::RotateX(zeus::degToRad(orientation.x)) *
|
||||
zeus::simd_floats f(orientation.mSimd);
|
||||
return zeus::CTransform::RotateZ(zeus::degToRad(f[2])) *
|
||||
zeus::CTransform::RotateY(zeus::degToRad(f[1])) *
|
||||
zeus::CTransform::RotateX(zeus::degToRad(f[0])) *
|
||||
zeus::CTransform::Scale(scale) +
|
||||
position;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ zeus::CAABox Platform::getVISIAABB(hecl::blender::Token& btok) const
|
||||
aabbOut = zeus::CAABox(aabb.first, aabb.second);
|
||||
}
|
||||
|
||||
if (aabbOut.min.x > aabbOut.max.x)
|
||||
if (aabbOut.min.x() > aabbOut.max.x())
|
||||
return {};
|
||||
|
||||
zeus::CTransform xf = ConvertEditorEulerToTransform4f(scale, orientation, location);
|
||||
|
||||
Reference in New Issue
Block a user