local ObjectPreview = {} ObjectPreview.__index = ObjectPreview ObjectPreview.Ref = {} ObjectPreview.IsMRTSupported = nil ObjectPreview.Scx, ObjectPreview.Scy = guiGetScreenSize() ObjectPreview.Fov = ({getCameraMatrix()})[8] function ObjectPreview:Constructor() if self.Initialized then return self end if (type(Require) ~= "function") then loadstring(exports["nc_libraries"]:extend("Extend"), "Require")() end local st = dxGetStatus() or {} self.IsMRTSupported = (tonumber(st.VideoCardNumRenderTargets) or 1) > 1 self.Initialized = true return self end local function _applyXY_WithParent_Respc(x, y) if not Draw or not Draw.Responsive or not Draw.Parent then return x, y end local parentActive = Draw.Parent:GetActive() local parent = parentActive and Draw.Parent:Get(parentActive) or {} if not Draw.InRenderTarget then local rx = Draw.Responsive:Respc(x) local ry = Draw.Responsive:Respc(y) if parent.x and parent.y then return parent.x + rx, parent.y + ry end return rx, ry end return x, y end local function _findEmptyEntry(t) for i, v in ipairs(t) do if v and v.enabled == false then return i end end return #t + 1 end local function _returnMaxValue(inTable) local out = {} for i, v in pairs(inTable) do out[i] = math.abs(v) end local n = #out local changed repeat changed = false n = n - 1 for i = 1, n do if out[i] > out[i + 1] then out[i], out[i + 1] = out[i + 1], out[i] changed = true end end until not changed return out[#out] end local function _toRad(x) return math.rad(x or 0) end local function _createElementMatrix(pos, rot) local rx, ry, rz = _toRad(rot[1]), _toRad(rot[2]), _toRad(rot[3]) return { { math.cos(rz) * math.cos(ry) - math.sin(rz) * math.sin(rx) * math.sin(ry), math.cos(ry) * math.sin(rz) + math.cos(rz) * math.sin(rx) * math.sin(ry), -math.cos(rx) * math.sin(ry), 0 }, { -math.cos(rx) * math.sin(rz), math.cos(rz) * math.cos(rx), math.sin(rx), 0 }, { math.cos(rz) * math.sin(ry) + math.cos(ry) * math.sin(rz) * math.sin(rx), math.sin(rz) * math.sin(ry) - math.cos(rz) * math.cos(ry) * math.sin(rx), math.cos(rx) * math.cos(ry), 0 }, { pos[1], pos[2], pos[3], 1 } } end local function _matrixMultiply(a, b) local out = {} for i = 1, #a do out[i] = {} for j = 1, #b[1] do local num = a[i][1] * b[1][j] for n = 2, #a[1] do num = num + a[i][n] * b[n][j] end out[i][j] = num end end return out end local function _getEulerAnglesFromMatrix(mat) local nz3 = math.sqrt(mat[2][1] * mat[2][1] + mat[2][2] * mat[2][2]) if nz3 == 0 then return 0, 0, 0 end local nz1 = -mat[2][1] * mat[2][3] / nz3 local nz2 = -mat[2][2] * mat[2][3] / nz3 local vx = nz1 * mat[1][1] + nz2 * mat[1][2] + nz3 * mat[1][3] local vz = nz1 * mat[3][1] + nz2 * mat[3][2] + nz3 * mat[3][3] return math.deg(math.asin(mat[2][3])), -math.deg(math.atan2(vx, vz)), -math.deg(math.atan2(mat[2][1], mat[2][2])) end local function _getPositionFromMatrixOffset(mat, pos) return (pos[1] * mat[1][1] + pos[2] * mat[2][1] + pos[3] * mat[3][1] + mat[4][1]), (pos[1] * mat[1][2] + pos[2] * mat[2][2] + pos[3] * mat[3][2] + mat[4][2]), (pos[1] * mat[1][3] + pos[2] * mat[2][3] + pos[3] * mat[3][3] + mat[4][3]) end local _lastTick = getTickCount() local _lastVel = {0, 0, 0} local _lastCamPos = {0, 0, 0} local function _getCamVelocity() if getTickCount() - _lastTick < 100 then return _lastVel[1], _lastVel[2], _lastVel[3] end _lastTick = getTickCount() local cx, cy, cz = getElementPosition(getCamera()) _lastVel = {cx - _lastCamPos[1], cy - _lastCamPos[2], cz - _lastCamPos[3]} _lastCamPos = {cx, cy, cz} return _lastVel[1], _lastVel[2], _lastVel[3] end local function _SCreate(shaderName, ...) local path = shaderName if _G.Shader and _G.Shader.Create then local el = _G.Shader:Create(path, ...) return el end return dxCreateShader(path, ...) end local function _SSetValues(shaderEl, values) if not isElement(shaderEl) then return false end if _G.Shader and _G.Shader.setValues then return _G.Shader:setValues(shaderEl, values) end for k, v in pairs(values or {}) do if type(v) == "table" then dxSetShaderValue(shaderEl, k, unpack(v)) else dxSetShaderValue(shaderEl, k, v) end end return true end local function _SSetValue(shaderEl, key, value) if not isElement(shaderEl) then return false end if _G.Shader and _G.Shader.setValue then return _G.Shader:setValue(shaderEl, key, value) end if type(value) == "table" then dxSetShaderValue(shaderEl, key, unpack(value)) else dxSetShaderValue(shaderEl, key, value) end return true end local function _SApply(shaderEl, textureName, element, appendLayers) if _G.Shader and _G.Shader.applyToWorldTexture then return _G.Shader:applyToWorldTexture(shaderEl, textureName, element, appendLayers) end return engineApplyShaderToWorldTexture(shaderEl, textureName, element, appendLayers) end local function _SRemove(shaderEl, textureName, element) if _G.Shader and _G.Shader.removeFromWorldTexture then local result, error_ = _G.Shader:removeFromWorldTexture(shaderEl, textureName, element) return end return engineRemoveShaderFromWorldTexture(shaderEl, textureName, element) end function ObjectPreview:_ensureRT(w_px, h_px) if not self.useMRT then return true end if not ObjectPreview.IsMRTSupported then return false end w_px = math.max(1, math.floor(tonumber(w_px) or 1)) h_px = math.max(1, math.floor(tonumber(h_px) or 1)) if not self.RT or not isElement(self.RT) then self.RT = dxCreateRenderTarget(w_px, h_px, true) self.RTW, self.RTH = w_px, h_px return self.RT and true or false end if self.RTW ~= w_px or self.RTH ~= h_px then destroyElement(self.RT) self.RT = dxCreateRenderTarget(w_px, h_px, true) self.RTW, self.RTH = w_px, h_px end return self.RT and true or false end function ObjectPreview:Create(element, x_px, y_px, w_percent, h_percent, rotX, rotY, rotZ, postGui, useMRT, attached) self:Constructor() if not isElement(element) then return false, "invalid element" end x_px = tonumber(x_px) or 0 y_px = tonumber(y_px) or 0 w_percent = tonumber(w_percent) or 0.2 h_percent = tonumber(h_percent) or 0.2 rotX = tonumber(rotX) or 0 rotY = tonumber(rotY) or 0 rotZ = tonumber(rotZ) or 0 postGui = postGui == true useMRT = useMRT == true attached = attached == true local renID = _findEmptyEntry(ObjectPreview.Ref) local inst = setmetatable({ element = element, alpha = 255, elementRadius = 1, elementRotation = {rotX, rotY, rotZ}, elementRotationOffsets = {0, 0, 0}, elementPositionOffsets = {0, 0, 0}, zDistanceSpread = 3.0, projection = {x_px, y_px, w_percent, h_percent, postGui}, useMRT = useMRT, attached = attached, visible = true, forceUpdate = false, panX = 0, panY = 0, zoom = 1.0, shaders = {}, RT = nil, RTW = 0, RTH = 0, _rtInterval = 2000, _lastRTTick = 0, TextureReplaces = {}, renID = renID, }, ObjectPreview) setElementAlpha(inst.element, 254) setElementStreamable(inst.element, false) setElementFrozen(inst.element, true) setElementCollisionsEnabled(inst.element, false) local minX, minY, minZ, maxX, maxY, maxZ = getElementBoundingBox(inst.element) inst.elementRadius = math.max(_returnMaxValue({minX, minY, minZ, maxX, maxY, maxZ}), 1) inst.bbox = { minX = minX, minY = minY, minZ = minZ, maxX = maxX, maxY = maxY, maxZ = maxZ, sizeX = (maxX or 0) - (minX or 0), sizeY = (maxY or 0) - (minY or 0), sizeZ = (maxZ or 0) - (minZ or 0) } local tempRadius = getElementRadius(inst.element) if tempRadius and tempRadius > inst.elementRadius then inst.elementRadius = tempRadius end if inst.useMRT then if not ObjectPreview.IsMRTSupported then return false, "MRT not supported" end local w_px2 = math.floor(inst.projection[3] * ObjectPreview.Scx) local h_px2 = math.floor(inst.projection[4] * ObjectPreview.Scy) if not inst:_ensureRT(w_px2, h_px2) then return false, "failed to create RT" end end inst.shaders[1] = _SCreate(inst.useMRT and "fx_pre_object" or "fx_pre_object_noMRT", 0, 0, false, "all") if not isElement(inst.shaders[1]) then return false, "failed to create shader" end local aspect = inst.useMRT and (inst.RTH / math.max(1, inst.RTW)) or (ObjectPreview.Scy / ObjectPreview.Scx) _SSetValues(inst.shaders[1], {sColorFilter1 = {1.75, 1.75, 1.75, 1}, sColorFilter2 = {0.11, 0.11, 0.11, 0}, sAlphaMult = 1, sFov = math.rad(ObjectPreview.Fov), sAspect = aspect }) if inst.useMRT and inst.RT then _SSetValue(inst.shaders[1], "secondRT", inst.RT) end _SApply(inst.shaders[1], "*", inst.element) ObjectPreview.Ref[renID] = { enabled = true, instance = inst } addEventHandler('onClientElementDestroy', inst.element, function() removeEventHandler('onClientElementDestroy', inst.element, debug.getinfo(1).func) if inst then inst:Destroy() end end, false) return inst end function ObjectPreview:ClearRTNow() if self.RT and isElement(self.RT) then dxSetRenderTarget(self.RT, true) -- true = clear dxSetRenderTarget() self._lastRTTick = getTickCount() return true end return false end function ObjectPreview:Destroy() self.visible = false self.forceUpdate = false if self.shaders and isElement(self.element) then for _, sh in ipairs(self.shaders) do if isElement(sh) then _SRemove(sh, "*", self.element) end end end if self.RT and isElement(self.RT) then destroyElement(self.RT) self.RT = nil end self:ClearAllTextureReplaces() local renID = self.renID if ObjectPreview.Ref[renID] then ObjectPreview.Ref[renID].enabled = false ObjectPreview.Ref[renID].instance = nil end setTimer(function() if self.shaders then for i, sh in ipairs(self.shaders) do if isElement(sh) then destroyElement(sh) end self.shaders[i] = nil end end end, 1, 1) self.element = nil return true end function ObjectPreview:SetProjection(x_px, y_px, w_percent, h_percent, postGui) self.projection[1] = tonumber(x_px) or self.projection[1] self.projection[2] = tonumber(y_px) or self.projection[2] self.projection[3] = tonumber(w_percent) or self.projection[3] self.projection[4] = tonumber(h_percent) or self.projection[4] if postGui ~= nil then self.projection[5] = postGui == true end if self.useMRT then local w_px2 = math.floor(self.projection[3] * ObjectPreview.Scx) local h_px2 = math.floor(self.projection[4] * ObjectPreview.Scy) self:_ensureRT(w_px2, h_px2) local aspect = (self.RTH / math.max(1, self.RTW)) for _, sh in ipairs(self.shaders or {}) do if isElement(sh) then _SSetValue(sh, "sAspect", aspect) if self.RT then _SSetValue(sh, "secondRT", self.RT) end end end end return true end function ObjectPreview:SetPan(px, py) local px, py = _applyXY_WithParent_Respc(px, py) if self.panX == px and self.panY == py then return true end self.panX = tonumber(px) or self.panX self.panY = tonumber(py) or self.panY self:ForceUpdate() return true end function ObjectPreview:SetZoom(z) z = tonumber(z) or self.zoom if z < 0.05 then z = 0.05 end if z > 10.0 then z = 10.0 end self.zoom = z return true end function ObjectPreview:SetRotation(rx, ry, rz) if self.elementRotation[1] == rx and self.elementRotation[2] == ry and self.elementRotation[3] == rz then return true end self.elementRotation = {tonumber(rx) or 0, tonumber(ry) or 0, tonumber(rz) or 0} self:ForceUpdate() return true end function ObjectPreview:SetRotationOffsets(x, y, z) self.elementRotationOffsets = {tonumber(x) or 0, tonumber(y) or 0, tonumber(z) or 0} return true end function ObjectPreview:SetPositionOffsets(x, y, z) self.elementPositionOffsets = {tonumber(x) or 0, tonumber(y) or 0, tonumber(z) or 0} return true end function ObjectPreview:SetDistanceSpread(z) self.zDistanceSpread = tonumber(z) or self.zDistanceSpread return true end function ObjectPreview:ForceUpdate() self.forceUpdate = true self:Update() return true end function ObjectPreview:SetVisible(b) self.visible = b setElementAlpha(self.element, b and 255 or 0) self:ForceUpdate() return true end function ObjectPreview:IsVisible() return self.visible end function ObjectPreview:SetAttached(b) self.attached = b == true return true end function ObjectPreview:GetRenderTarget() return self.RT end function ObjectPreview:SetTextureReplace(worldTextureName, textureOrPath) if not isElement(self.element) then return false, "invalid element" end if type(worldTextureName) ~= "string" or worldTextureName == "" then return false, "invalid worldTextureName" end self.TextureReplaces = self.TextureReplaces or {} local newTex = nil local owned = false if type(textureOrPath) == "string" then if not fileExists(textureOrPath) then return false, "texture file not found" end newTex = dxCreateTexture(textureOrPath) if not isElement(newTex) then return false, "failed to create texture" end owned = true elseif isElement(textureOrPath) then newTex = textureOrPath owned = false else return false, "invalid textureOrPath" end if self.TextureReplaces[worldTextureName] then local rep = self.TextureReplaces[worldTextureName] if rep.ownedTexture and rep.texture and isElement(rep.texture) then destroyElement(rep.texture) end rep.texture = newTex rep.ownedTexture = owned if rep.shader and isElement(rep.shader) then _SSetValue(rep.shader, "sTexture0", newTex) end return true end local sh = _SCreate(self.useMRT and "fx_pre_object_replace" or "fx_pre_object_replace_noMRT", 0, 0, false, "all") if not isElement(sh) then if owned and isElement(newTex) then destroyElement(newTex) end return false, "failed to create replace shader" end local aspect = self.useMRT and (self.RTH / math.max(1, self.RTW)) or (ObjectPreview.Scy / ObjectPreview.Scx) _SSetValues(sh, {sColorFilter1 = {1.75, 1.75, 1.75, 1}, sColorFilter2 = {0.11, 0.11, 0.11, 0}, sAlphaMult = 1, sFov = math.rad(ObjectPreview.Fov), sAspect = aspect }) if self.useMRT and self.RT then _SSetValue(sh, "secondRT", self.RT) end _SSetValue(sh, "sTexture0", newTex) _SApply(sh, worldTextureName, self.element) self.TextureReplaces[worldTextureName] = { shader = sh, texture = newTex, ownedTexture = owned, worldName = worldTextureName } table.insert(self.shaders, sh) return true end function ObjectPreview:RemoveTextureReplace(worldTextureName) if not self.TextureReplaces or not self.TextureReplaces[worldTextureName] then return false end local rep = self.TextureReplaces[worldTextureName] if rep.shader and isElement(rep.shader) then _SRemove(rep.shader, rep.worldName or worldTextureName, self.element) destroyElement(rep.shader) end if rep.ownedTexture and rep.texture and isElement(rep.texture) then destroyElement(rep.texture) end self.TextureReplaces[worldTextureName] = nil return true end function ObjectPreview:ClearAllTextureReplaces() if not self.TextureReplaces then self.TextureReplaces = {} return true end for worldName, rep in pairs(self.TextureReplaces) do if rep.shader and isElement(rep.shader) then _SRemove(rep.shader, rep.worldName or worldName, self.element) destroyElement(rep.shader) end if rep.ownedTexture and rep.texture and isElement(rep.texture) then destroyElement(rep.texture) end self.TextureReplaces[worldName] = nil end self.TextureReplaces = {} return true end function ObjectPreview:Update() if not isElement(self.element) then return false end local x_px, y_px, w_percent, h_percent, postGui = unpack(self.projection) local drawW = math.floor(w_percent * ObjectPreview.Scx) local drawH = math.floor(h_percent * ObjectPreview.Scy) if self.useMRT then self:_ensureRT(drawW, drawH) end local cameraMatrix = getElementMatrix(getCamera()) local rotationMatrix = _createElementMatrix({0, 0, 0}, self.elementRotation) local positionMatrix = _createElementMatrix(self.elementRotationOffsets, {0, 0, 0}) local transformMatrix = _matrixMultiply(positionMatrix, rotationMatrix) local multipliedMatrix = _matrixMultiply(transformMatrix, cameraMatrix) local distTemp = self.zDistanceSpread local offs = self.elementPositionOffsets local posX, posY, posZ = _getPositionFromMatrixOffset(cameraMatrix, { offs[1], 1.6 * self.elementRadius + distTemp + offs[2], offs[3] }) local rotX, rotY, rotZ = _getEulerAnglesFromMatrix(multipliedMatrix) local velX, velY, velZ = _getCamVelocity() local vecLen = math.sqrt(velX * velX + velY * velY + velZ * velZ) local camCom = {cameraMatrix[2][1] * vecLen, cameraMatrix[2][2] * vecLen, cameraMatrix[2][3] * vecLen} velX, velY, velZ = (velX + camCom[1]), (velY + camCom[2]), (velZ + camCom[3]) if not self.attached then setElementPosition(self.element, posX + velX, posY + velY, posZ + velZ, false) setElementRotation(self.element, rotX, rotY, rotZ, "ZXY") end local resW, resH = ObjectPreview.Scx, ObjectPreview.Scy if self.useMRT and self.RT and self.RTW and self.RTH then resW, resH = self.RTW, self.RTH end local panX = self.panX or 0 local panY = self.panY or 0 for _, sh in ipairs(self.shaders or {}) do if isElement(sh) then _SSetValues(sh, { sAlphaMult = (self.visible and 1 or 0), sCameraPosition = cameraMatrix[4], sCameraForward = cameraMatrix[2], sCameraUp = cameraMatrix[3], sElementOffset = {0, -distTemp, 0}, sWorldOffset = {-velX, -velY, -velZ}, sScreenResPx = { resW, resH }, sMoveObject2D = { x_px + panX, y_px + panY }, sPanPx = { panX, panY }, sScaleObject2D = { self.zoom, self.zoom }, sRealScale2D = { 1, 1 }, sProjZMult = 2 }) if self.useMRT and self.RT then _SSetValue(sh, "secondRT", self.RT) end end end return true end function ObjectPreview:UpdateRT() local now = getTickCount() if self.visible and self.RT and isElement(self.RT) and (now - self._lastRTTick >= self._rtInterval or self.forceUpdate) then dxSetRenderTarget(self.RT, true) dxSetRenderTarget() self._lastRTTick = now self.forceUpdate = false end end _G.ObjectPreview = ObjectPreview return ObjectPreview