自定义一个简单的操控器类

osg视图操作关于Manipulator 想看干货,还是FreeSouth总结的东西,写的很全 http://www.d3dweb.com/Documents/201203/31-22251384929.html以下是我自己项目的记录及碎碎念,为了梳理和加深印象: 1.KeySwitchMatrixManipulator类可以看做一个各类操作器的管理类,用按键来切换这些操作器。比如‘1’会被切换到TrackBall‘2’会被切换到Dri 阅读详情

目录

1. 前言

2. 操控器需求

3. 功能实现

3.1. 预备知识

3.2. 代码实现

3.3. 代码难点说明


1. 前言

       osg已经自己实现了很多操控器类,这些操控器类存放在osg源码目录下的src\osgGA目录。感兴趣的童鞋,可以自己去研究源码。下面两篇博文是我研究osg的键盘切换操控器和动画路径操控器之后写的,感兴趣的可以参考学习:

        虽然说osg实现了很多常用的操控器类,但现实中的业务需求是各种各样的,当osg自己实现的操控器不能满足平时的业务需求时,就需要自己开发操控器,本博文通过实现一个简单的操控器,来说明如何定制开发自己的操控器类。

2. 操控器需求

        

 图1

       如上图,实现一个网格,网格在X、Y轴范围为[-5, 5],单元格的长宽都为0.5,网格颜色为白色,坐标轴位于(0, 0, 0)处,实现一个操控器,操控器功能必须满足如下要求:

  • 按住键盘←键,能逆时针转动整个网格。
  • 按住键盘→键,能顺时针转动整个网格。
  • 起始时视点位于(0, -15, 15)处。
  • 当鼠标滚轮朝向人的方向转动时,此时网格变大。
  • 当鼠标滚轮背向人的方向转动,此时网格变小。
  • 按住空格键,网格回到原始状态。

即操控器要能实现如下效果:

图2 

3. 功能实现

3.1. 预备知识

       三维场景中的操控器都有一个共同特点,即它们都采用实时修正观察矩阵(也就是观察者的观察位置和姿态)的方式实现平滑导航浏览。而此处又有一个重要的结论:相机在世界坐标中的位置姿态矩阵,等于观察者的观察位置和姿态的逆矩阵。假设相机在世界坐标系中的位置姿态矩阵为M,观察者在世界坐标系中的观察位置和姿态矩阵为V,则有:                 

                                    M = V的逆

根据物理学相对性原理很好理解这种互逆关系,相机向前(左)运动等同于观察点向后(右)运动,反之亦然。关于这一结论更详细的描述,请参见:浅谈在操控器类中,为何要通过osgGA::CameraManipulator的逆矩阵改变视点位置博文。 

       osg提供了操控器类的基类CameraManipulator,该基类定义了实现操控器的所有接口。如果要定制自己的操控器类,则必须根据自己的需求,重写CameraManipulator类的Virtual开头的函数。CameraManipulator存放在osg源码目录下的include\osgGA目录。

       关于场景(本例是指网格)的拉近拉远,参见:osg下如何将物体拉近拉远博文。

3.2. 代码实现

           main.cpp文件如下:

#include<osgViewer/Viewer>
#include<osgDB/readFile>
#include<osgDB/writeFile>
#include<osgDB/FileUtils>
#include "mySimpleManipulator.h"

// 创建地板网格
osg::Geode* createFloorGrid()
{
   auto pGeode = new osg::Geode;
    auto pQuardGeomery = new osg::Geometry();
    pGeode->addDrawable(pQuardGeomery);

    auto pCoordArray = new osg::Vec3dArray;

    osg::DrawElementsUShort* pElementsUShort = new osg::DrawElementsUShort(GL_LINES, 84); // 共84个点

    auto elementIndex = 0;
    for (auto yIndex = -5.0; yIndex <= 5.0; yIndex += 0.5) // Y轴方向21条直线,42个点
    {
        pCoordArray->push_back(osg::Vec3d(-5.0, yIndex, 0.0));
        (*pElementsUShort)[elementIndex++] = pCoordArray->size() - 1;  // 记录每个点的索引,下同
        pCoordArray->push_back(osg::Vec3d(5.0, yIndex, 0.0));
        (*pElementsUShort)[elementIndex++] = pCoordArray->size() - 1 ;
    }

    (*pElementsUShort)[elementIndex++] = 0; // (-5.0, -5.0, 0.0)点的索引为0
    (*pElementsUShort)[elementIndex++] = pCoordArray->size() - 2;// (-5.0, 5.0, 0.0)点的索引
    (*pElementsUShort)[elementIndex++] = pCoordArray->size() - 1;// (5.0, 5.0, 0.0)点的索引
    (*pElementsUShort)[elementIndex++] = 1;// (5.0, -5.0, 0.0)点的索引为0

    for (auto xIndex = -4.5; xIndex <= 4.5; xIndex += 0.5) // X轴方向有19条直线,38个点
    {
        pCoordArray->push_back(osg::Vec3d(xIndex, -5.0, 0.0));
        (*pElementsUShort)[elementIndex++] = pCoordArray->size() - 1 ;
        pCoordArray->push_back(osg::Vec3d(xIndex, 5.0, 0.0));
        (*pElementsUShort)[elementIndex++] = pCoordArray->size() - 1;
    }
  
    pQuardGeomery->setVertexArray(pCoordArray);
    
    // 使格子线颜色为白色
    auto pColorArray = new osg::Vec4Array;
    pColorArray->push_back(osg::Vec4(1.0, 1.0, 1.0, 1.0));
    pQuardGeomery->setColorArray(pColorArray, osg::Array::Binding::BIND_OVERALL);

    auto pNormalArray = new osg::Vec3Array();
    pNormalArray->push_back(osg::Vec3(0, 0, 1));
    pQuardGeomery->setNormalArray(pNormalArray, osg::Array::Binding::BIND_OVERALL);

    pQuardGeomery->addPrimitiveSet(pElementsUShort);

    return pGeode;
}

int main(int argc, char *argv[])
{
    osgViewer::Viewer viewer;
    auto pAxis = osgDB::readRefNodeFile(R"(E:\osg\OpenSceneGraph-Data\axes.osgt)");
    if (nullptr == pAxis)
    {
        OSG_WARN << "axes node is nullpr!";
        return 1;
    }

    auto pRoot = new osg::Group();
    pRoot->addChild(pAxis);
    viewer.setSceneData(pRoot);

    auto pFloorGrid = createFloorGrid();
    pRoot->addChild(pFloorGrid);

    // 创建一个自定义的操控器对象
    auto pMySimpleManipulator = new CMySimpleManipulator();
    pMySimpleManipulator->setNode(pRoot);
    viewer.setCameraManipulator(pMySimpleManipulator);

    // 设置相机位置,注意:最开始时相机和观察点是重合的
    pMySimpleManipulator->setHomePosition(osg::Vec3d(0.0, -15.0, 15.0), osg::Vec3d(0.0, 0.0, 0.0), osg::Vec3(0, 0, 1));
    
    viewer.run();
}

 mySimpleManipulator.h文件如下:

#pragma once
#include<osgGA/CameraManipulator>
using namespace osgGA;
class CMySimpleManipulator :
    public CameraManipulator
{
public:
    CMySimpleManipulator();
    ~CMySimpleManipulator();
public:

    /** set the position of the matrix manipulator using a 4x4 Matrix.*/
    virtual void setByMatrix(const osg::Matrixd& matrix) override;

    /** set the position of the matrix manipulator using a 4x4 Matrix.*/
    virtual void setByInverseMatrix(const osg::Matrixd& matrix) override;

    /** get the position of the manipulator as 4x4 Matrix.*/
    virtual osg::Matrixd getMatrix() const override;

    /** get the position of the manipulator as a inverse matrix of the manipulator, typically used as a model view matrix.*/
    virtual osg::Matrixd getInverseMatrix() const override;

    /** Handle events, return true if handled, false otherwise. */
    virtual bool handle(const GUIEventAdapter& ea, GUIActionAdapter& us) override;

    /**
        Move the camera to the default position.
        May be ignored by manipulators if home functionality is not appropriate.
        */
    virtual void home(const GUIEventAdapter&, GUIActionAdapter&) override;

    /** Manually set the home position, and set the automatic compute of home position. */
    virtual void setHomePosition(const osg::Vec3d& eye, 
                                 const osg::Vec3d& center, 
                                 const osg::Vec3d& up, 
                                 bool autoComputeHomePosition = false) override;
    
    
    virtual void setNode(osg::Node*) override;

private:

    void setTransformation(const osg::Vec3d& eye, const osg::Vec3d& center, const osg::Vec3d& up);

    // 更新观察点位置
    void updateEyePos(const GUIEventAdapter& ea, GUIActionAdapter& us);
private:
    osg::ref_ptr<osg::Node> _spNode;
    osg::Vec3d _center;
    double _distance;
    osg::Quat _rotation;
    float _rotateZAngle{0};
    osg::Vec3d _initEye;
    float _sina{0.0};
    float _cosa{ 0.0 };
};

mySimpleManipulator.cpp文件如下:

#include "mySimpleManipulator.h"
#include<osgViewer/Viewer>
#include<iostream>
//#include <math.h>
CMySimpleManipulator::CMySimpleManipulator()
{
	
}
CMySimpleManipulator::~CMySimpleManipulator()
{

}

/** set the position of the matrix manipulator using a 4x4 Matrix.*/
void CMySimpleManipulator::setByMatrix(const osg::Matrixd& matrix)
{
	
}

/** set the position of the matrix manipulator using a 4x4 Matrix.*/
void CMySimpleManipulator::setByInverseMatrix(const osg::Matrixd& matrix)
{

}

/** get the position of the manipulator as 4x4 Matrix.*/
osg::Matrixd CMySimpleManipulator::getMatrix() const
{
    return osg::Matrixd::translate(0., 0., _distance) *
        osg::Matrixd::rotate(_rotation) *
        osg::Matrixd::translate(_center);
}

/** get the position of the manipulator as a inverse matrix of the manipulator, typically used as a model view matrix.*/
osg::Matrixd CMySimpleManipulator::getInverseMatrix() const
{
    return osg::Matrixd::translate(-_center) *
        osg::Matrixd::rotate(_rotation.inverse()) *
        osg::Matrixd::translate(0.0, 0.0, -_distance);
}
 
/** Handle events, return true if handled, false otherwise. */
bool CMySimpleManipulator::handle(const GUIEventAdapter& ea, GUIActionAdapter& us)
{
    auto pViewer = (osgViewer::Viewer*)(&us);
    switch (ea.getEventType())
    {
        case GUIEventAdapter::KEYDOWN:
        {
            int nKeyCode = ea.getKey();
            if (GUIEventAdapter::KEY_Left == nKeyCode)
            {
                _rotateZAngle += 1.0;
                updateEyePos(ea, us);
            }
            else if (GUIEventAdapter::KEY_Right == nKeyCode)
            {
                _rotateZAngle -= 1.0;
                updateEyePos(ea, us);
            }
            else if (GUIEventAdapter::KEY_Space == nKeyCode)
            {
                _homeEye = _initEye;
                _rotateZAngle = 0.0;
                home(ea, us);
            }
        }
        break;
        case GUIEventAdapter::SCROLL:
        {
            auto center = _spNode->getBound().center();
            auto scrollingMotion = ea.getScrollingMotion();
            auto scrollingIsMotion = false;
            if (GUIEventAdapter::SCROLL_DOWN == scrollingMotion) // 滚轮朝向人的方向转动
            {
                if (3 < _distance) // 防止拉得过近
                {
                    scrollingIsMotion = true;
                    _distance -= 0.5;
                }
            }
            else if (GUIEventAdapter::SCROLL_UP == scrollingMotion)// 滚轮背向人的方向转动
            {
                if (_distance < 50) // 防止拉得过远
                {
                    scrollingIsMotion = true;
                    _distance += 0.5;
                }
            }

            if (scrollingIsMotion)
            {
                _homeEye = center + osg::Vec3d(0, 0, 1) * _distance;
                pViewer->getCamera()->setViewMatrixAsLookAt(_homeEye, center, osg::Vec3(0, 0, 1));
            }

            pViewer->requiresRedraw();
            pViewer->requestContinuousUpdate(false);
        }
    } // end switch

	return CameraManipulator::handle(ea, us);
}

// 更新观察点位置
void CMySimpleManipulator::updateEyePos(const GUIEventAdapter& ea, GUIActionAdapter& us)
{
    _homeEye.z() = _distance * _sina;
    auto temp = _distance * _cosa;
    auto radians = osg::DegreesToRadians(_rotateZAngle);
    _homeEye.y() = -temp * std::cos(radians);
    _homeEye.x() = -temp * std::sin(radians);

    home(ea, us);
}

void CMySimpleManipulator::setHomePosition( const osg::Vec3d& eye,
                                            const osg::Vec3d& center,
                                            const osg::Vec3d& up,
                                            bool autoComputeHomePosition /*= false*/)
{
    _homeEye = eye;
    _homeCenter = center;
    _homeUp = up;

    // 记录观察点初始位置,以便等下按空格键能重置会初始位姿
    _initEye = _homeEye;  

    // 计算观察点即眼睛和物体中心即(0, 0, 0)点连线和Y轴负半轴的夹角的正弦值、余弦值
    auto angle = std::atan2(std::fabs(eye.z()) / std::fabs(eye.y()), 1.0);
    _sina = std::sinf(angle);
    _cosa = std::cosf(angle);
}

void CMySimpleManipulator::home(const GUIEventAdapter&ea, GUIActionAdapter&aa)
{
	setTransformation(_homeEye, _homeCenter, _homeUp);

	auto pViewer = (osgViewer::Viewer*)(&aa);
	pViewer->requiresRedraw();
	pViewer->requestContinuousUpdate(false);
}

void CMySimpleManipulator::setNode(osg::Node*pNode)
{
	_spNode = pNode;
}

void CMySimpleManipulator::setTransformation(const osg::Vec3d& eye, const osg::Vec3d& center, const osg::Vec3d& up)
{
    osg::Vec3d lv(center - eye); // 相机指向物体中心(这里指世界坐标系中的原点)的向量

    osg::Vec3d f(lv);
    f.normalize();// 相机指向物体中心方向

    // 计算相机其它两个轴的方向
    osg::Vec3d s(f ^ up);
    s.normalize();

    osg::Vec3d u(s ^ f);
    u.normalize();

    // 计算相机姿态矩阵,即旋转矩阵
    osg::Matrixd rotation_matrix(s[0], u[0], -f[0], 0.0f,
                                 s[1], u[1], -f[1], 0.0f,
                                 s[2], u[2], -f[2], 0.0f,
                                 0.0f, 0.0f, 0.0f, 1.0f);

    _center = center;
    _distance = lv.length(); // // 相机距离物体中心(这里指世界坐标系中的原点)的距离
    _rotation = rotation_matrix.getRotate().inverse(); // 相机的位置姿态矩阵的逆是观察点位置姿态矩阵

}

3.3. 代码难点说明

          调用操控器的home函数后,相机将回到程序设置的相机初始位置,即调用操控器类的setHomePosition函数设置的位置。在home函数中调用setTransformation函数来计算相机的姿态矩阵,即朝向,也即旋转矩阵。因为观察点矩阵和相机矩阵互逆,所以可以根据互逆关系求出观察点矩阵。最开始时观察点和相机处于世界坐标系下的同一位置。观察点矩阵是通过操控器类的如下函数获取的:

/** get the position of the manipulator as 4x4 Matrix.*/
osg::Matrixd CMySimpleManipulator::getMatrix() const
{
    return osg::Matrixd::translate(0., 0., _distance) *
        osg::Matrixd::rotate(_rotation) *
        osg::Matrixd::translate(_center);
}

由线性代数矩阵方面的知识,我们知道:矩阵乘积的逆等于矩阵的逆的相反顺序的程序,即满足如下公式:

图3 

所以将getMatrix函数中矩阵乘积求逆即为如下函数(注意:对于平移来说,直接将平移向量中的各个分量分别取反就取逆;对于矩阵来说就是取逆矩阵):

/** get the position of the manipulator as a inverse matrix of the manipulator, typically used as a model view matrix.*/
osg::Matrixd CMySimpleManipulator::getInverseMatrix() const
{
    return osg::Matrixd::translate(-_center) *
        osg::Matrixd::rotate(_rotation.inverse()) *
        osg::Matrixd::translate(0.0, 0.0, -_distance);
}
 

而getInverseMatrix函数的返回值即为相机矩阵。当按住键盘←、→键或滚动鼠标滚轮,改变观察点位置和姿态时,需要将相机矩阵返回到外层。在viewer.cpp第1212行附近的更新阶段void Viewer::updateTraversal()中,会调用如下:

_cameraManipulator->updateCamera(*_camera);

来更新相机位姿,其中_cameraManipulator为操控器类对象,也即main.cpp中的pMySimpleManipulator对象。上面代码的updateCamera函数在操控器基类CameraManipulator定义如下:

 /** update the camera for the current frame, typically called by the viewer classes.
            Default implementation simply set the camera view matrix. */
        virtual void updateCamera(osg::Camera& camera) { camera.setViewMatrix(getInverseMatrix()); }

即相机的位姿更新就在该处实现的,即前文说的操控器类将相机矩阵即getInverseMatrix()函数返回值返回到外层了。

       代码中重写了setHomePosition函数,在该函数中算出了观察点和物体中心即(0, 0, 0)点连线与Y轴负半轴夹角的正弦值、余弦值,便于后面按下←、→键时实时更新观察点坐标。在转动网格时,始终保持观察点和物体中心即(0, 0, 0)点连线与网格所在平面夹角不变。       

     updateEyePos函数实时更新观察点坐标,需要说明的是:_rotateZAngle的0°位置和Y轴负半轴重合,由Y轴负半轴非0的某个点指向坐标原点(0, 0, 0),逆时针时,_rotateZAngle增加1°;顺时针时,_rotateZAngle减小1°。

k8s 实现自定义控制器-原理篇 文章最开头实现的控制器虽然也能实现控制器功能,但并不是官方推荐的编程模式,现在来看下官方推荐的自定义控制器的编程模式。在之前直接写informer.AddEventHandler不同,这里引入了一个工作队列,EventHandler里的逻辑就是把收到的消息发送到工作队列中,然后有一段代码不断从工作队列中消费消息,而这段代码就被称作control loop 控制循环。client 包提供了现成的工作队列。 阅读详情

相关推荐

UE4蓝图实现按键切换操控角色:从原理到实践

在游戏开发中,玩家控制器(Player Controller)与角色(Pawn)的交互是核心机制之一,其原理在于控制器通过“附身”(Possess)来操控游戏世界中的实体。理解这一框架对于实现动态游戏逻辑至关重要,其技术价值在于能够灵活管理玩家输入与游戏对象的绑定关系,广泛应用于角色切换、多人协作原型、能力测试等场景。本文聚焦于使用UE4蓝图(Blueprint)可视化编程,通过游戏模式(GameMode)集中管理状态,结合输入检测与对象引用,实现运行时按键快速切换操控角色的功能,为新手提供一条高效、直观的

chenshixi3325的博客 315

osg操控器之键盘切换操控器osgGA::KeySwitchMatrixManipulator

osg操控器之键盘切换操控器osgGA::KeySwitchMatrixManipulator

danshiming的博客 1612

六、osg操作器实现

osgGA::CameraManipulator是一个支持自定义相机操作的操作器,支持用户自己定义鼠标、键盘操作来操作相机。常见的自定义鼠标键盘操作需求,比如: 使用键盘控制相机旋转、平移 使用鼠标操作相机旋转、平移 控制场景绕某个视点旋转 第一人称视角的的相机操作 等等

cangqiongxiaoye的博客 1769

osg::DrawElements*系列函数及GL_QUAD_STRIP、GL_QUADS绘制四边形效率对比

函数绘制图元比较多,本文以绘制四边形为例子,以。绘制图元,如:三角形、四边形等,一般用。来讲解怎样绘制四边形,及。的不同、它们之间的效率。

danshiming的博客 2566

HCIA重点知识

华为认证ICT工程师,是华为认证体系中用于标识个人能力在某一技术领域达到工程师级别的证明,表示通过认证的人员具备安装、配置、运行 ICT 设备,并进行基础故障排除的能力。抽象语言 -----> 编码 -----> 二进制 -----> 电信号 -----> 处理电信号信号的转换标准:协议,是一种允许电子设备相互交流的一系列标准的规则。即开放系统参考模型OSI/RM,简称“OSI”。OSI参考模型将计算机网络分为七个层次,自上而下依次为:应用层,表示层,会话层,传输层,网络层,数据链路层,物理层。1、应用层:

Awen0201的博客 2542

OC5021B 100V宽电压输入 LED车灯/大功率照明 DC降压型恒流驱动器

适用于LED大功率汽车大灯/大功率照明 OC5021B 100V宽电压输入 5A电流设计 DC-DC降压型恒流驱动IC

Handsome63841990的博客 2417

TypeScript 教程(八):装饰器与高级类型操控

在前几章中,我们学习了 TypeScript 的基础知识、函数与对象类型、接口与类、泛型编程、以及模块与命名空间。在本章中,我们将深入探讨 TypeScript 中的装饰器与高级类型操控。装饰器是一种特殊的声明,它可以被附加到类、方法、访问器、属性或参数上,用来修改它们的行为。高级类型操控则包括类型别名、映射类型、条件类型和工具类型等,帮助我们更加灵活地定义和操作类型。TS 入门(一):TypeScript 简介与安装TS 入门(二):Typescript类型与类型注解。

前端探索者 2309

无人机测绘航线生成算法

JavaScript开发和测试无人机测绘航线的自动生成方法,设计可行,算法可靠

ly1302116的专栏 5235

Karabiner-Elements开发者指南:构建自定义键盘驱动模块

Karabiner-Elements是一个强大的macOS键盘自定义工具,允许开发者创建高度定制化的键盘驱动模块。本文将深入探讨如何构建自定义键盘驱动模块,利用Karabiner-Elements的核心架构实现复杂的键盘重映射功能。 ## 🛠️ 开发环境配置 首先,确保你的开发环境满足以下要求: - macOS 15+ 操作系统 - Xcode 26+ 开发工具 - Command Lin

gitblog_01193的博客 921

【已解决,极简单!!】求助,R语言读取csv文件各种报错

【已解决】本来之前没问题,今天更新了Rstudioh以及R,就各种报错了。也看了目前能读取到的文件,从第一个中文字符直接就没有了。也就是说 还是读取中文字符的问题?新人初学,求大佬轻喷!

youyouluming945的博客 3878

【零基础学习CAPL】——CAN报文的发送(CRC——ChecSum值)

本章主要介绍带有CRC值的报文发送一般报文可使用CANoe的IG模块直接发送,但带有CRC值的报文就只能编写脚本进行发送,以下详细讲解在配合Panel面板情况下,如何进行带有循环冗余码的报文进行报文的发送。

LOVE135149的博客 1913

自定义List容器,自定义容器迭代器

自定义迭代器接口 package sky.java.innerclass; /** * 迭代器 */ public interface Selector { boolean end(); void next(); Object current(); } 自定义容器 , 与容器内迭代器实现 package sky.java.innerclass; ...

愿世界和平 542

【NI Multisim 14.0编辑环境——项目管理器】

主窗口包括标题栏、菜单栏、工具栏、工作区域、电子表格视图(信息窗口)、状态栏及项目管理器7个部分。 上期我们了解了工具栏,今天我们继续了解项目管理器。 一、项目管理器 在原理图设计中经常用到的工作面板有“设计工具箱”面板、“SPICE网表查看器”面板及“LabVIEW 协同仿真终端”面板。 1.“设计工具箱”面板 基本位于工作界面左侧,主要用于层次电路的显示。启动软件,默认创建的“设计1”以分层的形式显示出来。 那么如何打开“设计工具箱”面板呢?如图所示:在该面板中显示 SPICE 网表的输入,输出

RL2698853114的博客 2035

Unity3DRuntimeTransformGizmo扩展开发:创建自定义变换工具的高级指南

Unity3DRuntimeTransformGizmo是一款强大的开源工具,它允许开发者在运行时为游戏对象添加类似Unity编辑器的变换 gizmo,支持移动、旋转和缩放操作。本文将深入探讨如何扩展这一工具,创建符合特定项目需求的自定义变换工具,帮助开发者解锁更高效的场景编辑体验。 ## 为什么选择自定义变换工具? 在游戏开发过程中,标准变换工具往往无法满足所有场景需求。无论是需要特殊的操控

gitblog_00187的博客 475

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

自学鸿蒙应用开发(33)- 在布局中使用自定义UI组件

在布局中使用自定义组件 开发一个自定义UI组件,当然会希望在布局中像原生组件那样使用它。就像下面这样: <?xml version="1.0" encoding="utf-8"?><DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:height="match_parent" ohos:width="match_parent" ohos:orientation="vert

面向对象思考 687

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

moonlight-android 虚拟控制器配置:自定义布局与手势映射终极指南

moonlight-android 是一款强大的 Android GameStream 客户端,让你能够在移动设备上流畅串流 PC 游戏。这款应用最吸引人的功能之一就是其高度可定制的虚拟控制器系统,让玩家可以完全按照个人习惯来配置游戏控制界面。🎮 ## 🌟 虚拟控制器配置基础 moonlight-android 的虚拟控制器系统位于 `app/src/main/java/com/limel

gitblog_00398的博客 1585

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

Java中的类与对象

本篇文章是基于Java编程思想整理的Java类与对象方面的相关知识,主要介绍了Java中的对象,数据类型以及创建类等基本知识。

龙源IT 的博客 1503

python dict key类型_Python——dict(自定义类作key)

Python的dict要求key为不可变数据类型,通常采用str或int,但在某些应用场景下,需要采用自定义类型对象作key,此时的自定义类需要实现两个特殊方法:__hash__、__eq__,用于哈希值的获取和比较定义狗类:class Dog():def __init__(self,name,color):self._n = nameself._c = colordef __hash__(sel...

weixin_39955355的博客 4926

终极指南:如何在10分钟内掌握Garak LLM安全测试工具

想要确保你的大型语言模型足够安全吗?Garak就是你的最佳选择!作为一名专业的LLM安全评估工具,Garak能够帮助你发现模型中的各种安全隐患,包括幻觉、数据泄露、提示操控、错误信息、不当内容生成、安全绕过等多种弱点。无论你是AI开发者还是安全研究员,这个工具都能为你提供全面的安全评估。 ## 🚀 快速安装Garak ### 使用pip一键安装 ```bash python -m pip i

gitblog_00145的博客 978

AI安全实战:从提示词注入到数据提取的靶场攻防演练

在人工智能应用广泛部署的背景下,大型语言模型(LLM)的安全已成为技术架构中不可或缺的一环。其核心风险源于模型对输入序列的预测机制,攻击者可利用此原理实施提示词注入,通过精心构造的输入覆盖或绕过系统预设指令,从而操控模型行为。这类攻击不仅威胁应用功能的完整性,更可能引发训练数据提取等严重数据泄露事件。从技术价值看,理解并防御这些漏洞是构建可信AI系统的基石,直接关系到智能客服、代码助手、内容审核等核心应用场景的安全稳定。本文以AI安全靶场ai-goat为实践载体,系统剖析了包括直接提示词注入、间接提示词注入

weixin_33748818的博客 484

ListView实现Item内部操作

很简单,用自定义的适配器实现BaseAdapter。 代码: package com.cn.hyh; import java.util.ArrayList; import java.util.HashMap; import android.app.Activity; import android.content.Context; import android.os.Bundl

heyouhua123yeah的专栏 526

使用Python创建简单的HTTP和FTP服务

不管工作中还是其他场合,经常会有文件分享的需求,比如自己下了一个4GB的游戏,同事下了一个800MB的软件,其他人如果也需要这些文件,显然直接分享是最快捷、最方便、最环保的方式了,如果再重新下,既浪费时间,又浪费电。Windows自带了文件夹共享的功能,不过这玩意儿,至今我还没仔细的去折腾过,也没搞明白原理,我一直使用FTP来分享文件,如果有需要的话再弄个HTTP服务也足够应付了,我的目的仅仅只是

madison's blog 544

浅谈OSG的默认视点方向

在OSG开发中,对视点的理解透彻是必须可少的,特别是在进行自定义操控器类的开发中,对视点的深刻理解,更是重要,否则自定义的操控器功能可能会不正常、不会按预想的那样。。默认视点方向为何如此重要?因为旋绕、移动操作最开始是建立在默认视点基础上,即以默认视点为基础进行旋转、移动,从而在视觉上让人觉得场景在变化。如果默认视点方向理解错了,则后续的旋绕、移动操作都是错的,从而导致场景的旋转、移动不是预想的。

danshiming的博客 1670

Spring Boot自动装配原理深度解析:从@Conditional到自定义Starter实战

在Java企业级开发中,依赖注入和配置管理是构建可维护应用的核心基础。Spring框架通过IoC容器实现了依赖反转,而Spring Boot在此基础上引入了自动装配机制,将“约定大于配置”理念工程化。其技术原理基于条件化Bean加载,通过@Conditional系列注解(如@ConditionalOnClass、@ConditionalOnMissingBean)动态判断运行环境,实现智能配置。这一机制极大提升了开发效率,减少了冗余的XML或Java Config代码。在实际应用场景中,开发者可通过理解自动

weixin_28475533的博客 342
上一篇: osg的 realize()函数剖析
下一篇: 三次参数样条曲线与Cardinal曲线
荆楚闲人
博客等级 码龄20年 3万+粉丝 473原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值