How to rotate a part taking into account it's position and direction?

image
So, I have created a rather simple blood splat module. However, it’s always the same pattern and it never turns to a different direction. What I want to do is, depending on what way it is facing and where it is, it turns slightly. Example:
image

--- /// SETUP /// ---
local BloodSplatterDecalIDs = {91099453641060, 113297356394128}
local BloodSplatterSoundIDs = {7578871246}
local RBXASSETID = "rbxassetid://"
--- /// SERVICES /// --*
local debris = game:GetService("Debris")
local tweenService = game:GetService("TweenService")
local goal = {}
goal.Transparency = 1

--- /// ATTRIBUTE VARIABLES /// ---
local DestroyTime = script:GetAttribute("BloodCleanUpTime")
local VisDestroyTime = script:GetAttribute("VisualizationCleanUpTime")
local FadeDelay = script:GetAttribute("FadeDelay")
local debugMode = script:GetAttribute("Debug")
local bloodVisible = script:GetAttribute("BloodPartVisible")
local Distance = script:GetAttribute("Distance")




local BloodModule = {}


local function createBloodPart(position, normal)
	-- // Create our part of varying sizes
	local randomSize = math.random(1,10)
	local bloodPart = Instance.new("Part")
	bloodPart.Size = Vector3.new(randomSize, 0.01, randomSize) 
	bloodPart.Anchored = true
	bloodPart.CanCollide = false
	bloodPart.CastShadow = false
	bloodPart.CanQuery = false
	bloodPart.BrickColor = BrickColor.new("Bright red")
	bloodPart.Name = "BloodSplat"
	bloodPart.Rotation = Vector3.new()
	if bloodVisible then
		bloodPart.Transparency = 0
	else
		bloodPart.Transparency = 1
	end
	local tweenInfo = TweenInfo.new(DestroyTime, Enum.EasingStyle.Linear, Enum.EasingDirection.In, 0, false, FadeDelay)
	-- // Calculate when to destroy the blood splat
	local amt = FadeDelay + DestroyTime + 0.5

	-- // Calculate right and up vectors for the CFrame
	local upVector = normal
	local forwardVector = upVector:Cross(Vector3.new(0, 1, 0))
	if forwardVector.Magnitude == 0 then
		forwardVector = upVector:Cross(Vector3.new(1, 0, 0))
	end
	local rightVector = forwardVector:Cross(upVector)
	bloodPart.CFrame = CFrame.fromMatrix(position, rightVector, upVector)
	bloodPart.Parent = workspace.BloodSys.Blood
	-- // Create the splat decal
	local bloodDecal = Instance.new("Decal")
	bloodDecal.Texture = RBXASSETID..tostring(BloodSplatterDecalIDs[math.random(1, #BloodSplatterDecalIDs)])
	bloodDecal.Face = Enum.NormalId.Top
	bloodDecal.Parent = bloodPart
	-- // Make it fade
	local tween = tweenService:Create(bloodDecal, tweenInfo, goal)
	tween:Play()

	debris:AddItem(bloodPart, amt)

	return bloodPart
end


local function visualizeRay(origin, direction)	
	local part = Instance.new("Part")
	part.Size = Vector3.new(0.2, 0.2, direction.Magnitude)
	part.CFrame = CFrame.new(origin + direction / 2, origin + direction)
	part.Anchored = true
	part.CanCollide = false
	part.CanQuery = false
	part.CastShadow = false
	part.BrickColor = BrickColor.new("Bright yellow")
	part.Transparency = 0.5
	part.Name = "RayVisualization"
	part.Parent = workspace.BloodSys.Visualizations

	debris:AddItem(part, VisDestroyTime)
end

local function castRayAndSpawnBlood(direction, humanoidRootPart)
	local rayOrigin = humanoidRootPart.Position
	local rayDirection = direction.Unit * Distance
	local character = humanoidRootPart.Parent
	
	local raycastParams = RaycastParams.new()
	raycastParams.FilterDescendantsInstances = {character}
	raycastParams.FilterType = Enum.RaycastFilterType.Exclude

	-- Check if visualization is enabled
	local visualize = script:GetAttribute("VisualizeRays")
	if visualize then
		visualizeRay(rayOrigin, rayDirection)
	end


	local rayResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)

	if rayResult then
		local hitPosition = rayResult.Position
		local hitNormal = rayResult.Normal
		
		if debugMode then
			print("Ray hit something!")
			print("Ray hit position: ", hitPosition)
			print("Ray hit normal: ", hitNormal)
			print("Ray hit ", rayResult.Instance)
		end
		
		createBloodPart(hitPosition, hitNormal)
	else
		if debugMode then
			print("Ray did not hit anything.")
		end
	end
end

local function getRandomDirection()
	-- // Generate a random direction vector in a 360° space
	local theta = math.random() * 2 * math.pi  -- Random angle in the horizontal plane
	local phi = math.acos(2 * math.random() - 1)  -- Random angle for the vertical plane

	-- // Convert spherical coordinates to Cartesian coordinates
	local x = math.sin(phi) * math.cos(theta)
	local y = math.sin(phi) * math.sin(theta)
	local z = math.cos(phi)

	return Vector3.new(x, y, z)
end

function BloodModule:Splatter(humanoidRootPart: Part, min: number, max: number)
	print("Splatter function called")
	local numberOfRays = math.random(min, max)  -- Randomly select between 4 and 8 rays to cast
	local id = RBXASSETID..tostring(BloodSplatterSoundIDs[math.random(1,#BloodSplatterSoundIDs)])
	local sound = Instance.new("Sound")
	sound.Name = "Splat"
	sound.SoundId = id
	sound.RollOffMode = Enum.RollOffMode.Linear
	sound.RollOffMaxDistance = 200
	sound.RollOffMinDistance = 0
	sound.Parent = humanoidRootPart
	
	local effectSound = Instance.new("PitchShiftSoundEffect")
	effectSound.Octave = math.random(0.75, 1.25)
	effectSound.Parent = sound
	
	sound:Play()
	debris:AddItem(sound, 5)
	for i = 1, numberOfRays do
		local direction = getRandomDirection()
		castRayAndSpawnBlood(direction, humanoidRootPart)
	end
end

return BloodModule

The script.

After you set the splatters cframe, you can rotate it around its up axis by multiplying it with CFrame.Angles() with a random value
Something like
bloodPart.CFrame = CFrame.fromMatrix(position, rightVector, upVector) * CFrame.Angles(0, math.random() * 2 * math.pi,0)

I think this worked, can’t really tell. Thank you!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.