The Fastest Camera System You'll Probably See Written For Roblox!

I can’t find as well. @4thAxis Am I looking at the wrong places or has it been removed?

1 Like

The ShiftLockCamera function is the over-the-shoulder camera. Should probably rename this.

Call these functions from the CameraSystem module:

Module.EnableShiftLockCamera
Module.DisableShiftLockCamera
1 Like

yes please rename that ---------

1 Like

Hey man, is there anyway I’d be able to disable the camera bobbing? I’m using the isometric view and it’s really obnoxious, love what you’ve done though :slight_smile:

Hi, how do I use this with R6?

1 Like

If we drop this directly in Character,
are we supposed to have a dummy PlayerModule?

There also appears to be no emulation of the default PopperCam in this implementation?

1 Like
  • Consideration of Luau->C/C++ bridge invocations: Biggest bottleneck Roblox games face. It is extremely expensive to bridge data between C++ and Luau often making calculations in Luau much faster than they could be in C++.

This is just plain wrong. Sorry, but every operation done in luau actually causes a C++ invocation.

It’s a rule of thumb that if you’re doing something that Roblox has already implemented, you’re doing something wrong. The C++ code will almost always be superior, now there are some edge cases where a luau implementation is far superior but this is most definitely not one of them.

I also notice a lot of your code is unoptimized such as calculating the determinant of the rotational matrix, which you do twice, following by a branch to check if it’s 0 or not. That calculation and branch can be avoided if you use the simple mathematical fact that a special orthogonal matrix (orthonormal matrix) will always have determinant 1, and furthermore on a bit of a tangent it’s inverse to be the transpose. So instead of computing the determinant manually you could simply just put

local determinant = 1 -- (or infact just inline the determinant altogether)

and be done.

I find it horrible you’re spreading poor advice to scripters and teaching them just blatantly false facts.

1 Like

I believe there is some truth to at least the CFrame library being a bottleneck because it has to allocate a lot of memory for bridging as opposed to the native Vector3 datatype which doesn’t have to allocate any.

CFrame:

--!strict
--!optimize 2

local Number: number = 1000000
local Table: {CFrame} = table.create(Number)

local GC: number = gcinfo()
local Start: number = os.clock()

for Integer: number = 1, Number do
	table.insert(Table, CFrame.new(0, 10, 0))
end

print(os.clock() - Start) -- 0.09560939995571971
print(gcinfo() - GC) -- 62500

Vector3:

--!strict
--!optimize 2

local Number: number = 1000000
local Table: {Vector3} = table.create(Number)

local GC: number = gcinfo()
local Start: number = os.clock()

for Integer: number = 1, Number do
	table.insert(Table, Vector3.new(0, 10, 0))
end

print(os.clock() - Start) -- 0.02291449997574091
print(gcinfo() - GC) -- 0

And this also is backed up by the fact other datatypes like Vector2 which you would assume would be faster than a Vector3 end up having results closer to that of the CFrame, again because it’s not a native type.

--!strict
--!optimize 2

local Number: number = 1000000
local Table: {Vector2} = table.create(Number)

local GC: number = gcinfo()
local Start: number = os.clock()

for Integer: number = 1, Number do
	table.insert(Table, Vector2.new(0, 10))
end

print(os.clock() - Start) -- 0.07641560002230108
print(gcinfo() - GC) -- 23438

Although I’m not some guru on the subject so I’m only inferring from my limited knowledge what these results mean ¯\_(ツ)_/¯

1 Like

That’s not part of the issue, he would end up calling CFrame.new() on his luau calculated components anyway, so the bottleneck is actually completely irrelevant. He should just leave the calculation to the C++ side of things.

Also the reason Vector3 has that gcinfo() stuff being 0 is because it’s not a userdata as opposed to CFrame’s which are full userdata (userdata with a metatable), it’s the native Vector3 type.

The source of this would be the luau source code which declares these basic types:

enum lua_Type
{
    LUA_TNIL = 0,     // must be 0 due to lua_isnoneornil
    LUA_TBOOLEAN = 1, // must be 1 due to l_isfalse


    LUA_TLIGHTUSERDATA,
    LUA_TNUMBER,
    LUA_TVECTOR,

    LUA_TSTRING, // all types above this must be value types, all types below this must be GC types - see iscollectable


    LUA_TTABLE,
    LUA_TFUNCTION,
    LUA_TUSERDATA,
    LUA_TTHREAD,

    // values below this line are used in GCObject tags but may never show up in TValue type tags
    LUA_TPROTO,
    LUA_TUPVAL,
    LUA_TDEADKEY,

    // the count of TValue type tags
    LUA_T_COUNT = LUA_TPROTO
};

As the comments say, the things below that line are GC types, which includes LUA_TUSERDATA which is a full userdata which is what a CFrame is, However, a Vector3 is a value type (the LUA_TVECTOR type). The same goes for Vector2’s, they are userdata, not a LUA_TVECTOR which is why they aren’t 0 on the gcinfo.

I also bring up that he mentions specifically invocations if you see his claims he basically says writing out all the equations for a CFrame then constructing it with CFrame.new is faster than calling CFrame.Angles. Now let us benchmark (using your style of bming :3) using his source code for the angles function which you can find above.

local function AnglesX(x)
	local Cosx = math.cos(x)
	local Sinx = math.sin(x)

	return CFrame.new(0, 0, 0, 1, 0, 0, 0, Cosx, -Sinx, 0 ,Sinx, Cosx)
end

local Number = 1e4
local Table = table.create(Number)
local Start = os.clock()

for Integer=1, Number do
    table.insert(Table, AnglesX(Integer))
end

print(os.clock() - Start) -- 0.003 to 0.004 range

Compared to using CFrame.Angles

local Number = 1e4
local Table = table.create(Number)
local Start = os.clock()

for Integer=1, Number do
    table.insert(Table, CFrame.Angles(Integer, 0, 0))
end

print(os.clock() - Start) -- 0.002 to 0.003 range

Clearly the test shows that CFrame.Angles is faster, not by much, but still faster which is why I find his claims about a 7000% increase incredibly thoughtless seeing that it’s not only not that much, but even slower than CFrame.Angles, especially if you consider the fact he could create an AnglesXY function which would obviously have a lot more of a performance impact due to having to do more operations and calculate more trigonometric functions as the construction of a rotational matrix by euler angles requires.

Hope this clearly up some of the confusion about anything I said and thank you for reading :slight_smile:

Tldr: Despite the fact of CFrame having a bottleneck, he calls CFrame.new anyway and using his provided source code benchmarks show not only is the 7000% speed increase a lie, but his method is slower than CFrame.Angles.

Edit: Incase you want to confirm the stuff about the types, run this script in studio:

print(type(CFrame.new())) -- userdata
print(type(Vector3.new())) -- vector
print(type(Vector2.new())) -- userdata

Edit 2: Wait, sorry, I didn’t notice that you mentioned the native types bruh… for goodness sake, ignore the majority of my post then, thats a bit annoying :confused:

2 Likes

Before I start, I would first like to address @EatingBeesForDinner intentions which are to troll however I’ll still address all of these claims since they are relevant to some more technical people.

For the record, I asked him for feedback on my math and self-admitted prior that I do have this little extraneous determinant check which was a few weeks after I released the module, hence also why I left this comment for those who would question this fastlerpcase function:

if Determinant~=0 then return false end		-- if det is 0, we don't have to calculute for matrix inverse; shortcut lerp. Otherwise, Roblox here probably benefits from SIMD hardware optimizations which typically use elimination methods. Technically orthogonal matrices shouldn't have det=0 but who said this can't be just used with orthongal matrices ;)

Had no idea this person would now come to make a public post about this one small calculation and then use it to brand my entire module as “unoptimized”. What he says is correct about his “simple mathematical fact”, but

calculating the determinant of the rotational matrix, which you do twice, following by a branch to check if it’s 0 or not. That calculation and branch…

isn’t as expensive as you think and to get down to the margins, I’ve already compensated enough for the transforms to gap Roblox’s default camera transforms so these little calculations barely impact the speed up. Mult and add operations are blazingly fast on modern architectures and the cost of the branch itself is also negligible.

Could you elaborate on this?

Sorry, but every operation done in luau actually causes a C++ invocation.

Lua->C++ invocations and LuaBridge invocations are actually 2 different mechanisms. “Luau->C/C++ bridge invocations” the “bridge” in the quote is supposed to hint to LuaBridge.

To keep it short, LuaBridge is a C++ library that allows C++ code to be called from Lua, and it is often used as a way to make C++ functions and classes available to Lua scripts. In the context of Roblox,

  • LuaBridge is used to provide access to certain C++ functions and classes from within Luau scripts such as CFrame.

  • A “Lua->C++ invocation” refers to a specific call to a C++ function or method from within a Lua script. This can happen when a function or method that is implemented in C++ is called from within a Lua script, either directly or through an intermediate function or method.

But generally speaking on the statement, “Sorry, but every operation done in luau actually causes a C++ invocation.” isn’t necessarily true. Luau is an interpreted language, which means that it is not compiled into machine code before it is executed. Instead, the Luau interpreter reads and executes the Luau code directly, without the need for a separate compilation step. This means that most operations in Luau will be executed directly by the interpreter, without the need to invoke any C++ code.

There are certain operations in Luau that do involve calling C++ functions or methods, such as when using certain built-in functions or even when calling functions from a C++ dynamic-link library (DLL) that has been imported into the Luau code. However, it is not really an accurate statement to say that every operation in Luau causes a C++ invocation. Here are a few examples of when a C++ invocation does happen:

  • Using certain built-in Luau functions or methods that are implemented in C++: Some of the built-in functions and methods in Luau are implemented in C++ for performance reasons. For example, the string.sub function, which extracts a substring from a string, is implemented in C++ in order to provide fast and efficient substring extraction.

  • Accessing certain properties or methods of objects that are implemented in C++: In some cases, objects in Luau may have properties or methods that are implemented in C++ in order to provide fast and efficient access to certain data or functionality. For example, in the Roblox platform, many of the methods and properties of BasePart objects are implemented in C++.

Here are a few examples of operations in Luau that do not involve calling C++ functions or methods:

  • Performing basic arithmetic operations: Basic arithmetic operations such as addition, subtraction, multiplication, and division can be performed in Luau using the + , - , * , and / operators, respectively. These operations are typically implemented directly in the Luau interpreter and do not involve calling C++ code.

  • Assigning values to variables: Assigning values to variables in Luau does not involve calling C++ code. For example:

local x = 123
local y = "hello"
  • Performing string concatenation: The .. operator can be used to concatenate two strings in Luau. This operation is typically implemented directly in the Luau interpreter and does not involve calling C++ code.
s = "hello" .. "world"
  • Accessing and modifying table elements: Tables are data structures that are used to store and organize data in Luau, and they can be accessed and modified using the [] operator. Accessing and modifying table elements does not involve calling C++ code.
t = { 1, 2, 3 }
t[1] = 4
print(t[2])

There are many other operations that also do not involve C++, such as defining and calling functions, using control structures like if and for , and so on. Furthermore, the implementation of these operators can be found in the file “lmathlib.c” in the Lua (don’t have the Luau source at hand but it’s probably the same too.) source code. Here is an excerpt from the source code that shows the implementation of the + operator: Here is an excerpt from the source code that shows the implementation of the + operator:

static int math_add (lua_State *L) {
  lua_Number d;
  if (lua_isinteger(L, 1) && lua_isinteger(L, 2)) {
    lua_Integer a = lua_tointeger(L, 1);
    lua_Integer b = lua_tointeger(L, 2);
    lua_pushinteger(L, a + b);
    return 1;
  }
  else {
    lua_Number a = lua_tonumber(L, 1);
    lua_Number b = lua_tonumber(L, 2);
    lua_pushnumber(L, a + b);
    return 1;
  }
}

This function implements the + operator for both integer and floating-point values. It uses the lua_isinteger and lua_tointeger functions to check whether the operands are integers, and if they are, it uses the lua_pushinteger function to push the result onto the Lua stack. If the operands are not integers, the function uses the lua_tonumber and lua_pushnumber functions to convert the operands to floating-point values and push the result onto the stack. This function does not involve calling any external C functions or libraries. For clarity, the functions that are used to manipulate the Lua stack and convert values between different types (such as lua_isinteger , lua_tointeger , lua_tonumber , lua_pushinteger , and lua_pushnumber ) are part of the Lua API and are implemented as part of the Lua interpreter.

If you are still having trouble distinguishing between these concepts, I recommend you look at Arseny Kapoulkine (zeux)'s annotations on his AABBs: Annotated source of fastComputeAABB and fasterComputeAABB with notes for expensive lines · GitHub This should very well enlighten you.

Lastly, I would like to end off with this cheeky statement, “I find it horrible you’re spreading poor advice to scripters and teaching them just blatantly false facts.”.

1 Like

The purpose of the cframe.angles example was to be affiliated with some transform as stated:

do something like: Transformation*CFrame.Angles(...) we can just multiply only 4 components and cancel other components out

Typically, you have these chains of CFrames to multiply by when you can just simplify the transformation as a whole matrix and use CFrame.new on the simplified matrix which would save you the overhead of LuaBridge invocations or even the heap allocation to create CFrame object. Yes obviously if you have equivalent computation in Lua and C++, then C++ will outperform in raw computing speed. That is unless you can find yourself edge cases that surpass the underlying implementations of the C++ library which is why these types of optimizations come specifically, not generally. Generality is the suppressor of optimizations!

1 Like

Awesome module. I remember a year ago trying to make a good Isometric camera and I could’nt do it. Thanks bro :heart:

The luau interpreter is written in C++, and thus, everything the interpreter does is, C++ code, well more specifically it would be assembly since it’s compiled. Now that’s besides the point, you went on a bunch of tangents on how operations don’t involve C++, could you please tell me how luau, without using C++, recognizes function declarations and parses that, because I’m pretty sure you’ve realised that it would be using systems written in C++, so it does have everything to do with C++.

I’m also curious on why you mentioned the math library at all? Where in my post did I mention anything about that.

I found it very funny how you say basic arithmetic operations do not involve calling C++ code, then you show just below the C++ code that is executed when an addition instruction is dispatched, LOL.

Now I wont respond to your other comment in a seperate post so I’ll just say this, despite the fact it is in regard to another CFrame, it is still slower. I can provide a lovely benchmark that shows JUST THIS
FACT! (although I do apologise for the misunderstanding in my comment)

l--!optimize 2

local BaseCF = CFrame.identity

local function Multiply4thMethod(CF, X)
	local Cosx, Sinx = math.cos(X), math.sin(X)

	local m11, m12, m13 = 1, 0, 0
	local m21, m22, m23 = 0 , Cosx, -Sinx
	local m31, m32, m33 = 0, Sinx, Cosx
	
	local x, y, z, R11, R12, R13, R21, R22, R23, R31, R32, R33 = CF:GetComponents()
	
	return CFrame.new(
		x, y, z,
		R11, R12*m22 + R13*m32, R12*m23 + R13*m33,
		R21, R22*m22 + R23*m32, R22*m23 + R23*m33,
		R31, R32*m22 + R33*m32, R32*m23 + R33*m33
	)
end

local function NormalMultiply(CF, X)
	return CFrame.Angles(X,0,0) * CF
end

local Total1 = 0
for i=1, 1e5 do
	local RandomCFrame = CFrame.Angles(math.random(),math.random(),math.random())  * BaseCF + Vector3.new(math.random(), math.random(), math.random())
	local RandomX = math.random()
	local bm = os.clock()
	Multiply4thMethod(RandomCFrame, RandomX)
	Total1+=os.clock()-bm
end
local Total2 = 0
for i=1, 1e5 do
	local RandomCFrame = CFrame.Angles(math.random(),math.random(),math.random())  * BaseCF + Vector3.new(math.random(), math.random(), math.random())
	local RandomX = math.random()
	local bm = os.clock()
	NormalMultiply(RandomCFrame, RandomX)
	Total2+=os.clock()-bm
end

print('4thAxis', Total1)
print('Normal', Total2)

I believe this is what you’re referring too? In any case, that is the Transformation*CFrame.Angles(x, 0, 0)

The results of this, on my first run, showed


Which for anyone who is having trouble seeing that, it is

4thAxis  0.15 
Normal 0.09

approximately.

So yes, your way is not 7000% faster, but about 40% slower.

I feel like there may have been a bit of misunderstanding with what “Calling C++ Code” meant, but anyway the lua bridge is not as slow as you think. The biggest bottleneck infact with CFrame.Angles(x, 0, 0) is the creation of userdata, not bridging data from lua to C++.

Anyway, as you did say, my post was kind of a troll, lmao. But some of the points had validity like your misinformation about your CFrame multiplication optimizations, but no hard feelings at all. You’re still my baby girl and I love you very much <3

2 Likes

The luau interpreter is written in C++, and thus, everything the interpreter does is, C++ code, well more specifically it would be assembly since it’s compiled. Now that’s besides the point, you went on a bunch of tangents on how operations don’t involve C++, could you please tell me how luau, without using C++, recognizes function declarations and parses that, because I’m pretty sure you’ve realised that it would be using systems written in C++, so it does have everything to do with C++.

I guess there’s some misunderstanding here, which you seem to agree, “I feel like there may have been a bit of misunderstanding with what “Calling C++ Code” meant”. The point was to show you the difference between the types of bridging. Before the new Luau VM rewrite, it was commonly known that it was written in C. There was a larger distinction between calling Roblox API C++ and Lua’s C API. Probably harder to understand the concept since they’re both C++ which is why there might be some misunderstanding here. Regardless, it’s pretty pointless. I’ll just say “Calling Lua’s API” and “Calling Roblox’s API” (commonly understood as bridging).

I’m also curious on why you mentioned the math library at all? Where in my post did I mention anything about that.

I gave you an example?

I found it very funny how you say basic arithmetic operations do not involve calling C++ code, then you show just below the C++ code that is executed when an addition instruction is dispatched, LOL.

There’s was a misunderstanding as I previously stated on the difference. I’ll call if Lua API for now, since that’s what it really is. Imagine you are playing a game of chess, and you make a move by physically moving a chess piece on the board. The movement of the piece is not caused by the laws of physics, but rather by your intention and action to move it. Similarly, while the addition operation in Luau may involve some underlying C++ code that is executed, the actual addition operation is initiated by your code in Lua through the Luau API, just as the movement of the chess piece is initiated by your intention and action in the game of chess. That’s what an API is :stuck_out_tongue:.

Also, your benchmarks are flawed :frowning: . First off, your benchmark were inherently biased, you’re creating unnecessary rotation matrix components. Really all you need to do is just this:

local function Multiply4thMethod(CF, X)
	local Cosx, Sinx = math.cos(X), math.sin(X)
	local NegSinx = -Sinx
	local x, y, z, R11, R12, R13, R21, R22, R23, R31, R32, R33 = CF:GetComponents()
	return CFrame.new(
		x, y, z,
		R11, R12*Cosx + R13*Sinx, R12*NegSinx + R13*Cosx,
		R21, R22*Cosx + R23*Sinx, R22*NegSinx + R23*Cosx,
		R31, R32*Cosx + R33*Sinx, R32*NegSinx + R33*Cosx
	)
end

So re-running the benchmarks. I get:
image

I also noticed that you were benchmarking wrong. I hope you’re aware that os.clock itself is pretty expensive to call and it can be pretty inconsistent itself. Technically speaking, but not exactly sure of, the os.clock() function returns the amount of CPU time used by the program so far, which means that it is affected by other processes running on the system. If other processes are running and consuming CPU time, the time measured by os.clock() may not accurately reflect the time taken by the benchmarked code. You’re only running a single test with calling the time function once. Then trying to average it out. To avoid any clock inaccuracies, your unbiased benchmark becomes this:

--!optimize 2

local BaseCF = CFrame.identity

local function Multiply4thMethod(CF, X)
	local Cosx, Sinx = math.cos(X), math.sin(X)
	local NegSinx = -Sinx
	local x, y, z, R11, R12, R13, R21, R22, R23, R31, R32, R33 = CF:GetComponents()
	return CFrame.new(
		x, y, z,
		R11, R12*Cosx + R13*Sinx, R12*NegSinx + R13*Cosx,
		R21, R22*Cosx + R23*Sinx, R22*NegSinx + R23*Cosx,
		R31, R32*Cosx + R33*Sinx, R32*NegSinx + R33*Cosx
	)
end

local function NormalMultiply(CF, X)
	return CF * CFrame.Angles(X,0,0)
end

local Clock1 = os.clock()
for i=1, 1e5 do
	local RandomCFrame = BaseCF * CFrame.Angles(math.random(),math.random(),math.random()) + Vector3.new(math.random(), math.random(), math.random())
	local RandomX = math.random()
	Multiply4thMethod(RandomCFrame, RandomX)
end
print(os.clock()-Clock1, "4thAxis")
local Clock2 = os.clock()
for i=1, 1e5 do
	local RandomCFrame = BaseCF * CFrame.Angles(math.random(),math.random(),math.random()) + Vector3.new(math.random(), math.random(), math.random())
	local RandomX = math.random()
	NormalMultiply(RandomCFrame, RandomX)
end
print(os.clock()-Clock2, "Normal")

Yielding more consistent benchmarks:
image

Here’s a more thorough breakdown using BoatBomber’s benchmark plugin:

local BaseCF = CFrame.identity
return {
	ParameterGenerator = function()
		return BaseCF * CFrame.Angles(math.random(),math.random(),math.random()) + Vector3.new(math.random(), math.random(), math.random()), math.random()
	end;
	Functions = {
		["4thAxis"] = function(Profiler, CF, X) 
			for i=1, 1e4 do
				local Cosx, Sinx = math.cos(X), math.sin(X)
				local NegSinx = -Sinx
				local x, y, z, R11, R12, R13, R21, R22, R23, R31, R32, R33 = CF:GetComponents()
					local _ =CFrame.new(
					x, y, z,
					R11, R12*Cosx + R13*Sinx, R12*NegSinx + R13*Cosx,
					R21, R22*Cosx + R23*Sinx, R22*NegSinx + R23*Cosx,
					R31, R32*Cosx + R33*Sinx, R32*NegSinx + R33*Cosx
					)
			end
		end;
		["Normal?"] = function(Profiler, CF, X)
			for i=1, 1e4 do
				local _ = CF * CFrame.Angles(X,0,0)
			end
		end;
	};
}

So conclusively speaking, my statement holds true. However, usually with the multiplication bit, I tend to know my components so I can take advantage of that and produce even faster speed ups. Regardless though, I plan on doing a little rewrite to optimize my camera even more and add additional features. For example, in some camera modes like isometric, some variables will stay constant so I can take advantage of that If I wanted.


nice benchmarks, and I mean, since you’re trying to be snarky here I’ll just add that you should probably test your FastCameraSystems before releasing it, considering that the FollowMouse mode doesn’t work. :slight_smile: Have a good day, I’ll be looking forward to your next reply with another bogus claim in 3 months.

Edit: Even IF the benchmarks were as you put, that’s still far from the claimed 7000% increase. It goes down to a margin that isn’t (precisely) measurable. Maybe 2% faster in one case, 5% slower in another, and it’s just a blur on which is objectively more fast and at that point I’d start opting to use the easiest to implement option, which is the “Normal?” way.

2 Likes

For the record, I sat in a VC with him and always got mine as winning. He basically rigged his benchmark:
image

Regardless, I got it tested on different hardware. It was pretty inconsistent. As I said, I’ll specifically try to optimize when I’m aware of the components of the first CFrame anyways. Otherwise, you’re basically suggesting to do any dimensional transformation with just CFrames. “still far from the claimed 7000% increase”, I plugged the benchmarks into a percent calculator, probably in the wrong order which is why the benchmark code is there.

at that point I’d start opting to use the easiest to implement option

Which is why someone already did the implementation for you? I reduced your GC footprint, you can open profiler now and you won’t see userdata spams take up your profiler :wink:

There’s no way you just said rigged when I did 6 and only 1 of them (the one you showed there) was in your favour :sob: just give it up lil bro

As I said, I’ll specifically try to optimize when I’m aware of the components of the first CFrame anyways.

This is just dumb, lol. It’s not like you knew the components ahead of time in the benchmark we talk about? I don’t see what you’re trying to say in the slightest, apart from irrelevant things to make it seem as if you came up with some holy grail of an argument. When really you just said “uh he faked them” and added some extra bloating text at the bottom.

Not to mention your “fastest camera module” can be HEAVILY optimized, for instance, take your isometric camera, which you have as:

Module.IsometricCamera = function(_, CameraDepth, HeightOffset, FOV)
    CameraDepth = CameraDepth or Configs.IsometricCameraDepth
    HeightOffset = HeightOffset or Configs.IsometricHeightOffset
    Camera.FieldOfView = FOV or Configs.IsometricFieldOfView
    DisableRobloxCamera()

    local Root = HumanoidRootPart.Position + Vector3.new(0, HeightOffset, 0)
    local Eye = Root + Vector3.new(CameraDepth, CameraDepth, CameraDepth)    
    
    local XAxis = Root-Eye
    if (XAxis:Dot(XAxis) <= Module.Epsilon) then 
        Camera.CFrame = CFrame.new(Eye.X, Eye.Y, Eye.Z, 1, 0, 0, 0, 1, 0, 0, 0, 1) 
    end
    XAxis = XAxis.Unit
    local Xx, Xy, Xz = XAxis.X, XAxis.Y, XAxis.Z
    local RNorm = (((Xz*Xz)+(Xx*Xx))) -- R:Dot(R), our right vector
    if RNorm <= Module.Epsilon and math.abs(XAxis.Y) > 0 then
        Camera.CFrame = CFrame.fromMatrix(Eye, -math.sign(XAxis.Y)*Vector3.zAxis, Vector3.xAxis)
    end
    RNorm = 1/(RNorm^0.5) -- take the root of our squared norm and inverse division
    local Rx, Rz = -(Xz*RNorm), (Xx*RNorm) -- cross y-axis with right and normalize
    local Ux, Uy, Uz = -Rz*(Rz*Xx-Rx*Xz), -(Rz*Rz)*Xy-(Rx*Rx)*Xy, Rx*(Rz*Xx-Rx*Xz) -- cross right and up and normalize.
    local UNorm = 1/((Ux*Ux)+(Uy*Uy)+(Uz*Uz))^0.5 -- inverse division and multiply this ratio rather than dividing each component
    Camera.CFrame = CFrame.new(
        Eye.X,Eye.Y,Eye.Z,
        Rx, -Xy*Rz, Ux*UNorm, 
        0, (Rz*Xx)-Rx*Xz, Uy*UNorm,
        Rz, Xy*Rx, Uz*UNorm
    )
end

and… lets see if we can change this at all… hmm

local RCF = CFrame.lookAt(Vector3.zero,-Vector3.one)
Module.IsometricCamera = function()
    local Character = game.Players.LocalPlayer.Character
    local CameraDepth = Configs.IsometricCameraDepth
    local HeightOffset = Configs.IsometricHeightOffset
    workspace.CurrentCamera.CFrame = RCF + Vector3.yAxis*HeightOffset + Vector3.one*CameraDepth + Character.HumanoidRootPart.Position
end

Interesting, right?
Now you might notice how I didn’t add the FOV there, because adding an FOV change in something that runs every frame is stupid. You should add it in the EnableIsometricCamera function, before binding to renderstep.
So even off the non-existant argument you had against creating an entirely expanded CFrame calculation for something that isn’t even faster, we now have your fastest camera module having 7 times as many lines for one thing, which again, is most definitely not faster since you can just cache the rotation for an isometric camera considering the only that changes on each update is the translation, yet you continue to calculate the rotation each frame. Incredible fastest camera module :clap: and after I pointed all this out you began whining in my DMs that you’re “going to rewrite it” yet still cling onto it as being “optimal” when it doesn’t even work (Check his “FastCameraSystem” if you’re curious, specifically the FollowMouse function, which just screws up the camera, but he’ll most likely patch it to hang onto the very shattered pride he still has), which is funny you just ignored when I mentioned that in my last reply, must’ve not seen it. :wink:

And, before you start whining about “well this is relevant to the actual topic” neither is the fact that you “know components ahead of time” since you don’t here, it’s just stupid. Stop trying to act as if you’ve created some more optimal way that doing something Roblox already has an interface for, when you haven’t, you’ve just expanded things to, I guess, flex math knowledge? And then give miscalculated statistics about. That’s all I have to say, thanks for reading :slight_smile:

And, considering I know how much you love optimization, think about using AxisAngles for things like that :slight_smile:

local BaseCF = CFrame.identity
return {
	ParameterGenerator = function()
		return CFrame.Angles(math.random(),math.random(),math.random()) * BaseCF + Vector3.new(math.random(), math.random(), math.random()), math.random()
	end;
	Functions = {
		["4thAxis"] = function(Profiler, CF, X) 
			for i=1, 1e2 do
				local Cosx, Sinx = math.cos(X), math.sin(X)
				local NegSinx = -Sinx
				local x, y, z, R11, R12, R13, R21, R22, R23, R31, R32, R33 = CF:GetComponents()
				local _ =CFrame.new(
					x, y, z,
					R11, R12*Cosx + R13*Sinx, R12*NegSinx + R13*Cosx,
					R21, R22*Cosx + R23*Sinx, R22*NegSinx + R23*Cosx,
					R31, R32*Cosx + R33*Sinx, R32*NegSinx + R33*Cosx
				)
			end
		end;
		["Normal??"] = function(Profiler, CF, X)
			for i=1, 1e2 do
				local _ = CFrame.Angles(X,0,0) * CF
			end
		end;
		['Chad Axis-Angles'] = function(Profiler,CF,X)
			for i=1,1e2 do
				local _ = CFrame.fromAxisAngle(Vector3.xAxis,X) * CF
			end
		end,
	};
}
2 Likes

Really good module!

I’m trying to make it lock-on to a target though… I was able to make the FaceCharacterToMouse face any object in the workspace, but how would I make the Over Shoulder Camera do the same?

Yes you’re right then, axis angles rotation does save the day. Thank you for thoroughly reviewing through my camera module for the past few months (not being sarcastic). At the time I was writing the math, I was benchmarking everything along the way so not sure what went wrong. This would definitely be nice for other game engines out there that don’t have the same CFrame library we do. Feel free to make a forked version with your optimizations.

This is fantastic, great work @4thAxis. Question though, would you be able to make a property for the camera modes that could use it (isometric, side-scrolling, top-down) for if you can zoom in and out as well as having a property to set a min and max distance of said zoom?