Depending on the running state of the script the glow and transparency appears to break.
The provided script only handles the scale and movement of the object, it doesn't set any texture or material values which makes me think its movement related.
  • Create a linkset of 65 cubes.
  • Set them all to the default white texture in the blinn-phong texture window.
  • Set the glow value to 1 and the transparent value to any non-zero value.
  • Create a new script and insert the following SLua script.
Watch the state of the glow and transparency when toggling the scripts running state.
-- Neon Zombie 2026.
-- SLua linkset movement testing
-- 65 links total: 1 root, 64 children. Child cube default scale dynamically set
-- Linkset
local FIRST_CHILD_LINK = 2
local LAST_CHILD_LINK = 65
local TOTAL_CHILDREN = LAST_CHILD_LINK - FIRST_CHILD_LINK + 1
local GRID_SIZE = 4
local CUBE_SIZE = 0.025
local MIN_SEPARATION = CUBE_SIZE * 2.0
local volume_area = 0.06
local TIMER_RATE = 0.02
local SPRING = 16.0
local DAMPING = 0.90
local MASS = 1.0
local MAX_DT = 0.033
-- Collision
local COLLISION_DIST = CUBE_SIZE * 1.75
local COLLISION_DIST_SQ = COLLISION_DIST * COLLISION_DIST
local REPULSION_FORCE = 2000.0
-- Localized Math
local m_sin = math.sin
local m_cos = math.cos
local m_sqrt = math.sqrt
local m_pi = math.pi
local m_random = math.random
local function v(x, y, z) return vector(x, y, z) end
local ZERO_VECTOR = v(0, 0, 0)
local ZERO_ROTATION = ll.Euler2Rot(ZERO_VECTOR)
local state = {}
local primCache = {}
local timerHandle = nil
local startTime = 0
local lastTime = 0
local cubeSizeVec = v(CUBE_SIZE, CUBE_SIZE, CUBE_SIZE)
-- Precompute float paths and stack positions, its much much faster.
local function buildCaches()
local gridHalf = ((GRID_SIZE - 1) * MIN_SEPARATION) * 0.5
for i = 0, TOTAL_CHILDREN - 1 do
local x = i % GRID_SIZE
local y = math.floor(i / GRID_SIZE) % GRID_SIZE
local z = math.floor(i / (GRID_SIZE * GRID_SIZE))
local stackPos = v(
(x * MIN_SEPARATION) - gridHalf,
(y * MIN_SEPARATION) - gridHalf,
(z * MIN_SEPARATION) - gridHalf
)
primCache[i] = {
stackTarget = stackPos,
-- Expanded movement area amplitudes
ax = 0.04 + m_random() * volume_area,
ay = 0.04 + m_random() * volume_area,
az = 0.02 + m_random() * volume_area,
sx = 0.8 + m_random() * 0.4,
sy = 0.8 + m_random() * 0.4,
sz = 0.6 + m_random() * 0.4,
phaseX = m_random() * 2 * m_pi,
phaseY = m_random() * 2 * m_pi,
phaseZ = m_random() * 2 * m_pi
}
state[i] = { pos = stackPos, vel = ZERO_VECTOR }
end
end
-- Generates the destination coordinate for a specific cube at time 't'
local function getFloatTarget(i, t)
local c = primCache[i]
return c.stackTarget + v(
m_sin(t * c.sx + c.phaseX) * c.ax,
m_cos(t * c.sy + c.phaseY) * c.ay,
m_sin(t * c.sz + c.phaseZ) * c.az
)
end
local function updatePhysics(t, dt, snap)
if dt > MAX_DT then dt = MAX_DT end
local batchParams = {}
local pIdx = 1
-- Immediate snap resolution
if snap then
for i = 0, TOTAL_CHILDREN - 1 do
local link = i + FIRST_CHILD_LINK
local target = primCache[i].stackTarget
state[i].pos = target
state[i].vel = ZERO_VECTOR
batchParams[pIdx] = PRIM_LINK_TARGET
batchParams[pIdx+1] = link
batchParams[pIdx+2] = PRIM_SIZE
batchParams[pIdx+3] = cubeSizeVec
batchParams[pIdx+4] = PRIM_POS_LOCAL
batchParams[pIdx+5] = target
batchParams[pIdx+6] = PRIM_ROT_LOCAL
batchParams[pIdx+7] = ZERO_ROTATION
pIdx = pIdx + 8
end
ll.SetLinkPrimitiveParamsFast(0, batchParams)
return
end
-- Calculate Base Forces
local currentForces = {}
for i = 0, TOTAL_CHILDREN - 1 do
currentForces[i] = (getFloatTarget(i, t) - state[i].pos) * SPRING
end
-- Collision Pass
for i = 0, TOTAL_CHILDREN - 1 do
local pi = state[i].pos
for j = i + 1, TOTAL_CHILDREN - 1 do
local pj = state[j].pos
local dx = pi.x - pj.x
local dy = pi.y - pj.y
local dz = pi.z - pj.z
local distSq = dx*dx + dy*dy + dz*dz
-- If inside collision radius (ignoring perfectly overlapping zero-distance vectors)
if distSq < COLLISION_DIST_SQ and distSq > 0.000001 then
local dist = m_sqrt(distSq)
local overlap = COLLISION_DIST - dist
-- Calculate normalized repulsion vector scaled by overlap severity
local pushMag = overlap * REPULSION_FORCE
local push = v((dx/dist)*pushMag, (dy/dist)*pushMag, (dz/dist)*pushMag)
-- Apply equal and opposite penalty forces
currentForces[i] = currentForces[i] + push
currentForces[j] = currentForces[j] - push
end
end
end
-- Batch Update
for i = 0, TOTAL_CHILDREN - 1 do
local link = i + FIRST_CHILD_LINK
local s = state[i]
-- Apply force to velocity, dampen, and update position
local accel = currentForces[i] / MASS
s.vel = (s.vel + accel * dt) * DAMPING
s.pos = s.pos + (s.vel * dt)
-- Dynamic rotation: makes the cubes tumble as they move
local rot = ll.Euler2Rot(v(s.vel.y * 2.0, s.vel.x * 2.0, 0))
batchParams[pIdx] = PRIM_LINK_TARGET
batchParams[pIdx+1] = link
batchParams[pIdx+2] = PRIM_POS_LOCAL
batchParams[pIdx+3] = s.pos
batchParams[pIdx+4] = PRIM_ROT_LOCAL
batchParams[pIdx+5] = rot
pIdx = pIdx + 6
end
if #batchParams > 0 then
ll.SetLinkPrimitiveParamsFast(0, batchParams)
end
end
local function onTimer()
local now = os.clock()
local dt = now - lastTime
lastTime = now
local t = now - startTime
-- Update physics (snap = false)
updatePhysics(t, dt, false)
end
-- Init
local function startup()
math.randomseed(math.floor(os.clock() * 100000))
buildCaches()
-- Snap all cubes to stack immediately
updatePhysics(0, 0, true)
startTime = os.clock()
lastTime = startTime
if timerHandle then
LLTimers:off(timerHandle)
end
timerHandle = LLTimers:every(TIMER_RATE, function()
local success, err = pcall(onTimer)
if not success then
ll.OwnerSay("Runtime Error in Timer: " .. tostring(err))
LLTimers:off(timerHandle)
timerHandle = nil
end
end)
ll.OwnerSay("Asteroid Simulation Running. Touch to reset and scatter.")
end
-- Resets the layout.
LLEvents:on("touch_start", function(detected)
-- Randomize the float targets for a new pattern on every click
buildCaches()
-- Snap them all to the tight grid and let physics push them apart on the next frame
updatePhysics(os.clock() - startTime, 0, true)
end)
startup()