Release Notes for 420

Notes for Release 420

46 Likes

lol

Client Difference Log

API Changes

Added Class PermissionsService : Instance [NotCreatable] [Service]
	Added Function Array PermissionsService:GetPermissions(string assetId) {RobloxScriptSecurity}
	Added Function void PermissionsService:SetPermissions(string assetId, Array permissions) {RobloxScriptSecurity}

Added Class PluginDebugService : Instance [NotCreatable] [Service]
Added Class DebuggablePlugin : Plugin

Added Property bool AngularVelocity.ReactionTorqueEnabled
Added Property float BallSocketConstraint.MaxFrictionTorque

Added Function Tuple StudioService:GizmoRaycast(Vector3 origin, Vector3 direction, RaycastParams raycastParams = RaycastParams.new()) {PluginSecurity}
Added Function Tuple WorldRoot:Raycast(Vector3 origin, Vector3 direction, RaycastParams raycastParams = RaycastParams.new())

Added Enum RaycastFilterType
	Added EnumItem RaycastFilterType.Blacklist : 0
	Added EnumItem RaycastFilterType.Whitelist : 1

Removed Event GuiService.InspectPlayerFromUserIdRequest

(Click here for a syntax highlighted version!)

23 Likes

What is the purpose of RayCast?

It appears to just be the raycasting functions with extra steps
It also says it returns tuple, but it appears to only return a RaycastResult.

local origin = Vector3.new(0,1,0)
local direction = Vector3.new(0,-2,0)
local params = RaycastParams.new()
-- why aren't the properties set in the constructor???
params.IgnoreWater = true
params.FilterType = 0 -- blacklist
params.FilterDescendantsInstances = {}
local result = workspace:Raycast(origin,direction,params)
local position = result and result.Position
local normal = result and result.Normal
local material = result and result.Material
-- how do i get the hit part?

And what does GizmoRaycast do?

4 Likes

I believe the intent is to consolidate the functionality of raycasting under one roof.

Data can be defined out of order (depending on what settings you actually want for the raycast) rather than being strictly tied to argument order.

6 Likes

Whats going to happen to Ray.new then? Or did I miss something.

2 Likes

Nothing, its just being superseded by this new functionality.

3 Likes

Why the new raycast API exists at all

The existing raycasting API surface is unworkable – FindPartOnRay WithIgnoreList/WhiteList take many parameters and return many results. Adding any further parameters / results to them would make them seriously messy to use. That’s a problem if we want to extend the raycasting API with more functionality, or add the ability to cast multiple rays / get multiple results from a single call. Add to this the fact that all those params and return values have to be added to both normal and Whitelist variants and you get quite a mess.

Why does it only do the same things that the old API did?

We ported the old raycasting functionality to the new API as a first step. Now that we have a more flexible / future proof API we can look into adding some of the things which have been possible at the engine level but missing from the existing API like support for collision groups.

Why doesn’t it take a Ray object?

Having FindPartOnRay take a Ray was a mistake. There is no way you’ll ever have an appropriate ray object “ready to go” so every single call you make to FindPartOnRay requires you to construct a Ray object only for the engine code to take it apart into two Vector3s again on the other end. It’s busywork that the engine no longer forces you do with the new API.

What does GizmoRaycast do?

It raycasts against attachments and constraint system gizmos in studio (Hence why it’s on StudioService, because those things only exist in Roblox Studio), so that plugins have some way to interact with these objects, especially with regards to clicking to select things.

Why does it return a Tuple

Because it returns nil if there wasn’t a hit. The only way for the existing reflection system to handle returning a value type or nil is as a Tuple result type.

How do I get the part

RaycastResult::Instance is the field the part is in, because if you got the RaycastResult from a GizmoRaycast it will be an Attachment, Constraint, or WeldConstraint, not a BasePart.

33 Likes

Maybe it should return `Tuple<bool, RayResult?> instead, or just provide a nil value for Hit like the current API technically does?

Also to my understanding, it should be possible to provide a nil value to some extent in the reflection system? Look into how BasePart.CustomPhysicalProperties is handling it?

1 Like

CustomPhysicalProperties is really weird and I don’t like it. It chooses to reflect as nil under some conditions, so even if the API description promises that you’re going to get one you might not.

I didn’t make it do this because it’s redundant. You’re going to have to check the bool part of the result anyways, so why not check the RaycastResult itself? I wanted it to be such that if you have a RaycastResult, it always represents an actual hit point on a raycast, that’s why I didn’t return a result with nil stuffed inside of it.

Overall, I want the API that’s the most flexible / robust. Raycasting is a pretty infrequent operation, there’s not going to be many unique places in your game code where you use a Raycast. Given that it doesn’t need to be a particularly succinct API.

3 Likes

Is there any chance you could get that onto the devhub soon? Would be nice to have that stuff documented from the get go instead of having to look this post up months down the line (or asking IX to get the exact same info you just gave).

A lot of it is superfluous but it might be nice to have GizmoRaycast documented since it sounds useful.

2 Likes

Worry not, it will be fully documented before being turned on. I was just giving the kind of nitty gritty context that the people who read the release notes are interested in.

6 Likes

Oh boy, another release without the WorldModel in public sight.

patch 420 tho

13 Likes

It appears to return none when there wasn’t a hit.

local o = Vector3.new(0,1,0)
local d = Vector3.new(0,-2,0)
local p = RaycastParams.new()
p.IgnoreWater = true
p.FilterType = Enum.RaycastFilterType.Whitelist
p.FilterDescendantsInstances = {}
print(select("#",workspace:Raycast(o,d,p))) --> 0

Also, FindFirstChild can return nil, yet it isn’t listed as returning tuple?

Maybe it has to do with it returning none?

1 Like

This is the main thing that bothers me about it returning a Tuple.

The expectation is that a Tuple is returning multiple things, and thus can’t be easily described within the limitations of Roblox’s reflection system.

RayResult on the other hand could be described as the return type even if it does return nil, because the behavior it presents right now ultimately makes it a nullable type, much like Instance and PhysicalProperties.

It may not be the prettiest solution on the backend, but the difference would go a long way in making the static API information more informative, both for auto-complete and for the DevHub itself.

Just some food for thought @stravant

2 Likes

My assumption was that Tuple was used to represent multiple values of any type, whereas Variant was used for a single value of any type. Does Raycast ever return multiple values? Some sleuthing reveals that Raycast returns zero values, not nil. This might explain why Tuple is used (0 or 1 values), but it’s definitely not appropriate. If a nil result is the route you’re going for a non-hit, then nil needs to be returned explicitly.

Also, similar to the previous APIs, the naming of the direction argument still gives the false impression that a ray is expected. What is really expected is a line segment formed by the two given points. It would be nice if this argument were renamed to properly reflect this. “Direction” implies that I’m supposed to supply a unit vector, or at least a vector that gets normalized, but this has never been the case. I get tricked by this pretty much every single time.

5 Likes

Alas… Variant can be any type except for void, it has to actually represent a value. I’ve already resolved to change it to use the PhysicalProperties solution, even though I don’t particularly like how it works internally. I forgot that the current approach would make it return 0 values, which is definitely not appropriate.

If you have a suggestion for an alternative I’m open to it. Do you think it would be better to call it “length” even though it’s a Vector?

I didn’t want to add a third max length parameter because it’s semantically redundant, any time you have a separate length available could call the equivalent Raycast(origin, direction * length) instead of Raycast(origin, direction, length), with the exact same number of characters and sometimes you’ll already have the direction+length vector ready to go and not want to have to separate it.

The forgetting length problem is interesting, I wasn’t aware of that. I never had a problem with it, because I know that you fundamentally can’t do an infinite length raycast, so I just know that I have to be passing some kind of length or it’s wrong.

“goal” seems fine as a name. Combining this with “origin” preserves the concept of direction, flowing from the origin to the goal. It also incites a metaphor of starting out from the origin in an attempt to reach the goal, but possibly hitting an obstruction along the way, and going no further once reaching the goal.

2 Likes

Would you also make it a global space position, rather than relative? “goal” strongly suggests a position in global coordinates.

Also, I’m somewhat open to including a length argument, whether or not to do that was close call when we designed the API, would you be happy with origin, direction, length, params as arguments?

1 Like

Please keep it with direction being a scaled direction vector because it’s much more convenient in both cases:

  1. if one has a unit length vector dir and a scalar length, in the old case one writes oldcast(...,dir*length,...) whereas in the proposed change it is newcast(...,dir,length,...). There isn’t much difference here
  2. Instead if one is presented with a scaled direction vector dir the difference is much worse: oldcast(...,dir,...) versus newcast(...,dir.unit,dir.magnitude,...)
  3. And if instead dir is a vector expression it’s even more complicated with separating the parameters

Aha! That’s the other gotcha that I always fall for. Even right now, I have no clue whether the direction vector is supposed to be local or global. I just assume it’s global, which is probably wrong.

Let’s consider the following APIs:

-- Ray API (direction gets normalized):
Raycast(origin: Vector3, direction: Vector3, length: number)

-- Line API (global points):
Raycast(origin: Vector3, goal: Vector3)

-- Combined API (offset is local to origin):
Raycast(origin: Vector3, offset: Vector3)

And the following types of inputs:

-- I want to shoot a ray. I have these variables:
local origin: Vector3
local direction: Vector3 -- normalized
local length: number

-- I want to draw a line. I have these variables:
local start: Vector3
local finish: Vector3

Finally, how to construct a call with each API and each input type:

-- Ray(ray):
Raycast(origin, direction, length)
-- Ray(line):
Raycast(start, finish-start, (finish-start).magnitude)

-- Line(ray):
Raycast(origin, direction*length+origin)
-- Line(line):
Raycast(start, finish)

-- Combined(ray):
Raycast(origin, direction*length)
-- Combined(line):
Raycast(start, finish-start)

My opinion:

The Ray API starts feeling too busy when constructing an input from a line. There’s also normalization to worry about (does this argument get normalized, or am I supposed to do it myself?)

The Line API, like Combined, is more succinct than the Ray API. Constructing a ray from a line seems relatively straightforward. The raycast itself is performed in global coordinates, so having two global points as the input result makes it easier to understand exactly what is going to happen. In general, it’s easiest to visualize a line when it’s drawn between two points. I prefer this one the most.

The Combined API looks like it’s the most simple for producing inputs, but requires the construction of a directed line segment. This consists of two Vector3s, so my mind immediately jumps to a line segment. In reality, one Vector3 represents a point, and the other represents a direction and length. Three separate concepts packed together to look like yet another concept might explain why it seems so unintuitive.


Ultimately, it would be best to look at how the current raycasting APIs are being used. To developers prefer to shoot rays, or draw lines? The API should reflect the most common scenario.

1 Like