hashit

本文介绍了一种在线处理字符串操作的方法,利用后缀平衡树动态维护字符串的插入和删除操作,并通过哈希技术高效计算不同连续子串的数量。

题目大意

你有一个字符串S,开始为空,现在有两种操作:
1. 在S后面加入一个字符c
2. 删除S最后一个字符(保证进行该操作时S不为空)
每次操作后输出当前S中有多少个不同的连续子串。

操作数不大于100000

在线做法

这道题可以离线建trie,然后打个sam。(然而我打的是在线)

在线维护字符串,维护插入、删除操作,很容易想到后缀平衡树。

如果字符串是静态的,统计S中不同子串个数的经典做法是用后缀数组,构造出height数组后统计答案。

由于后缀平衡树维护后缀的顺序,所以现在我们可以动态算height数组。采用hash,可以二分height[i]的大小,然后hash判断两个后缀相同长度的连续一段是否相同即可。
时间复杂度O(nlogn)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

const int maxn=100005,mo=5371297,pri=832189,M[2]={67,71};

typedef long long LL;

typedef unsigned long long ULL;

const LL Inf=(LL)1<<60;

int n,root,fa[maxn],son[maxn][2],fix[maxn],len,pre[maxn],nxt[maxn],height[maxn];

LL l[maxn],r[maxn],rank[maxn],ans;

ULL Hash[2][maxn],Power[2][maxn];

char s[maxn],c[maxn];

void rebuild(int x,LL L,LL R)
{
    if (!x) return;
    l[x]=L; r[x]=R; rank[x]=l[x]+r[x];
    rebuild(son[x][0],l[x],rank[x]>>1);
    rebuild(son[x][1],rank[x]>>1,r[x]);
}

void Rotate(int x,int t,LL l,LL r)
{
    int y=fa[x];
    if (y==root) root=x;else
    {
        if (son[fa[y]][0]==y) son[fa[y]][0]=x;else son[fa[y]][1]=x;
    }
    fa[x]=fa[y];
    son[y][t]=son[x][t^1]; fa[son[y][t]]=y; son[x][t^1]=y; fa[y]=x;
    rebuild(x,l,r);
}

bool cmp(int x,int y)
{
    return s[x]<s[y] || s[x]==s[y] && rank[x-1]<rank[y-1];
}

int ran(int x)
{
    return (LL)(s[x]+x)*pri%mo;
}

bool Same(int x,int y,int l)
{
    if (x<l) return 0;
    for (int j=0;j<2;j++)
        if (Hash[j][x]-Hash[j][x-l]*Power[j][l]!=Hash[j][y]-Hash[j][y-l]*Power[j][l]) return 0;
    return 1;
}

int lcp(int x,int y)
{
    if (x>y) x^=y^=x^=y;
    int l,r,mid;
    for (l=1,r=x+1,mid=l+r>>1;l<r;mid=l+r>>1)
        if (Same(x,y,mid)) l=mid+1;else r=mid;
    return l-1;
}

void update(int x)
{
    ans-=nxt[x]-height[nxt[x]];
    height[x]=lcp(x,pre[x]);
    height[nxt[x]]=lcp(nxt[x],x);
    ans+=x-height[x]+nxt[x]-height[nxt[x]];
}

void insert(int x,int i,LL l,LL r)
{
    LL mid=l+r>>1;
    if (cmp(x,i))
    {
        if (son[i][0]) insert(x,son[i][0],l,mid);
        else
        {
            son[i][0]=x; fa[x]=i; rebuild(x,l,mid);
            pre[x]=pre[i]; nxt[pre[x]]=x;
            nxt[x]=i; pre[i]=x;
        }
        if (fix[i]>fix[son[i][0]]) Rotate(son[i][0],0,l,r);
    }else
    {
        if (son[i][1]) insert(x,son[i][1],mid,r);
        else
        {
            son[i][1]=x; fa[x]=i; rebuild(x,mid,r);
            nxt[x]=nxt[i]; pre[nxt[x]]=x;
            pre[x]=i; nxt[i]=x;
        }
        if (fix[i]>fix[son[i][1]]) Rotate(son[i][1],1,l,r);
    }
}

void Delete(int x)
{
    while (son[x][0]>0 || son[x][1]>0)
        if (!son[x][0] || son[x][1]>0 && fix[son[x][0]]>fix[son[x][1]]) Rotate(son[x][1],1,l[x],r[x]);
        else Rotate(son[x][0],0,l[x],r[x]);
    if (root==x) root=0;else
    {
        ans-=x-height[x]+nxt[x]-height[nxt[x]];
        if (x==son[fa[x]][0]) son[fa[x]][0]=0;else son[fa[x]][1]=0;
        pre[nxt[x]]=pre[x]; nxt[pre[x]]=nxt[x];
        height[nxt[x]]=min(height[nxt[x]],height[x]);
        //height[nxt[x]]=lcp(nxt[x],pre[x]);
        ans+=nxt[x]-height[nxt[x]];
    }
}

int main()
{
    scanf("%s",c+1);
    n=strlen(c+1);
    Power[0][0]=Power[1][0]=1;
    for (int i=1;i<=n;i++)
        for (int j=0;j<2;j++) Power[j][i]=Power[j][i-1]*M[j];
    for (int i=1;i<=n;i++)
    {
        if (c[i]=='-') Delete(len--);else
        {
            s[++len]=c[i];
            fix[len]=ran(len);
            fa[len]=son[len][0]=son[len][1]=0;
            for (int j=0;j<2;j++) Hash[j][len]=Hash[j][len-1]*M[j]+s[len]-'a';
            if (len==1)
            {
                root=1;
                l[1]=0; rank[1]=r[1]=Inf;
                ans=1;
                printf("1\n");
                continue;
            }
            insert(len,root,0,Inf);
            update(len);
        }
        printf("%lld\n",ans);
    }
    return 0;
}
#ifndef CUSTOMDEVICESMANAGER_H #define CUSTOMDEVICESMANAGER_H #include "scada.h" #include "qtpropertybrowser.h" #include "qtpropertymanager.h" #include <QScopedPointer> #include <QVariantMap> #include <QList> class CustomDevicesManagerPrivate; // DeviceManager class CustomDevicesManager : public QtAbstractPropertyManager { Q_OBJECT public: explicit CustomDevicesManager(QObject* parent = nullptr); ~CustomDevicesManager() override; // get the substring item manager QtStringPropertyManager* subStringPropertyManager() const; // get the ScadaHash struct. Graphics::ScadaHash value(const QtProperty* property) const; bool isManagedProperty(const QtProperty *property) const; QString getValueText(const QtProperty* property) { return valueText(property); } void initDeviceProperty(QtProperty* property) { initializeProperty(property); } void uninitDeviceProperty(QtProperty* property) { uninitializeProperty(property); } // get the property value corresponding to the attribute use QString. QString valueText(const QtProperty* property) const override; public slots: // set the scada property with ScadaHash. void setValue(QtProperty* property, const Graphics::ScadaHash& val); signals: void valueChanged(QtProperty* property, const Graphics::ScadaHash& val); void buttonClicked(QtProperty* property); protected: void initializeProperty(QtProperty* property) override; void uninitializeProperty(QtProperty* property) override; private: QScopedPointer<CustomDevicesManagerPrivate> d_ptr; Q_DECLARE_PRIVATE(CustomDevicesManager) Q_DISABLE_COPY(CustomDevicesManager) // according to the Qt base manager settings. Q_PRIVATE_SLOT(d_func(), void slotStringChanged(QtProperty*, const QString&)) Q_PRIVATE_SLOT(d_func(), void slotPropertyDestroyed(QtProperty*)) }; // DevicesManagerPrivate(PIMPL) class CustomDevicesManagerPrivate { CustomDevicesManager* q_ptr; Q_DECLARE_PUBLIC(CustomDevicesManager) public: void slotStringChanged(QtProperty* property, const QString& value); void slotPropertyDestroyed(QtProperty* property); // map from QtProperty to the ScadaHash. typedef QMap<const QtProperty*, Graphics::ScadaHash> PropertyValueMap; PropertyValueMap m_values; // the string manager to the editor lineEdior. QtStringPropertyManager* m_stringPropertyManager = nullptr; // let all these parameters be in one group. QtGroupPropertyManager* m_groupPropertyManager = nullptr; // map the master property to the childern parameters. QMap<const QtProperty*, QList<QtProperty*>> m_propertyToScadas; // 主属性→参数复合属性列表(参数1、参数2...) QMap<const QtProperty*, QList<QtProperty*>> m_scadaToFields; // 参数复合属性→字段子属性列表(LineEdit 对应的字段) QMap<const QtProperty*, QtProperty*> m_fieldToScada; // 字段子属性→所属参数复合属性 QMap<const QtProperty*, QtProperty*> m_scadaToDevice; // 参数复合属性→所属主属性 QMap<const QtProperty*, QString> m_fieldToMember; // 字段子属性→对应数据的键名(QVariantMap 的 key) QMap<const QtProperty*, Graphics::ScadaKey> m_scadaToKey; // Scada复合属性→对应的ScadaKey(用于显示) }; #endif // CUSTOMDEVICESMANAGER_H #include "customdevicesmanager.h" #include <QCoreApplication> #include <QDebug> #include <QMetaProperty> // ====================== private class(CustomDevicesManagerPrivate)====================== void CustomDevicesManagerPrivate::slotStringChanged(QtProperty* fieldProp, const QString& value) { // // 反向查找:字段→Scada复合属性→主属性 // QtProperty* scadaProp = m_fieldToScada.value(fieldProp, nullptr); // QtProperty* deviceProp = m_scadaToDevice.value(scadaProp, nullptr); // QString memberName = m_fieldToMember.value(fieldProp, ""); // if (!scadaProp || !deviceProp || memberName.isEmpty()) return; // // 查找对应的 ScadaKey 和 ScadaPtr // Graphics::ScadaHash& scadaHash = m_values[deviceProp]; // Graphics::ScadaKey scadaKey = m_scadaToKey.value(scadaProp); // if (!scadaHash.contains(scadaKey)) return; // // 更新 ScadaPtr 的元属性值 // Graphics::ScadaPtr scadaPtr = scadaHash[scadaKey]; // if (!scadaPtr) return; // // 按元属性名赋值 // if (memberName == "code") scadaPtr->setCode(value); // else if (memberName == "domain") scadaPtr->setDomain(value); // else if (memberName == "type") scadaPtr->setType(value.toInt()); // else if (memberName == "substation") scadaPtr->setSubstation(value); // else if (memberName == "tableName") scadaPtr->setTableName(value); // else if (memberName == "realTableName") scadaPtr->setRealTableName(value); // else if (memberName == "deviceId") scadaPtr->setDeviceId(value); // else if (memberName == "deviceCode") scadaPtr->setDeviceCode(value); // emit q_ptr->valueChanged(deviceProp, scadaHash); QtProperty* scadaProp = m_fieldToScada.value(fieldProp, nullptr); QtProperty* deviceProp = m_scadaToDevice.value(scadaProp, nullptr); QString memberName = m_fieldToMember.value(fieldProp, ""); if (!scadaProp || !deviceProp || memberName.isEmpty()) return; Graphics::ScadaHash& scadaHash = m_values[deviceProp]; Graphics::ScadaKey scadaKey = m_scadaToKey.value(scadaProp); if (!scadaHash.contains(scadaKey)) return; Graphics::ScadaPtr scadaPtr = scadaHash[scadaKey]; if (!scadaPtr) return; //Get properties through QMetaObject reflection. const QMetaObject* metaObj = scadaPtr->metaObject(); int propIndex = metaObj->indexOfProperty(memberName.toUtf8().constData()); if (propIndex == -1) { qDebug() << "[slotStringChanged] Scada don't have " << memberName << " property,return"; return; } QMetaProperty metaProp = metaObj->property(propIndex); if (metaProp.isWritable()) { // QString/int if (metaProp.type() == QMetaType::QString) { metaProp.write(scadaPtr.get(), QVariant(value)); // } else if (metaProp.type() == QMetaType::Int) { metaProp.write(scadaPtr.get(), QVariant(value.toInt())); } else if (metaProp.type() == QMetaType::Double) { metaProp.write(scadaPtr.get(), QVariant(value.toDouble())); } else { qDebug() << "[slotStringChanged] " << metaProp.typeName() << "(" << memberName << ")"; } } else { qDebug() << "[slotStringChanged] Property" << memberName << "can't writable, jump"; } emit q_ptr->valueChanged(deviceProp, scadaHash); } void CustomDevicesManagerPrivate::slotPropertyDestroyed(QtProperty* fieldProp) { // 清理映射关系 // -A| // ----B1| // -------C1| // -------C2| QtProperty* scadaProp = m_fieldToScada.take(fieldProp);//B if (scadaProp) {//B QtProperty* deviceProp = m_scadaToDevice.value(scadaProp, nullptr);//A if (deviceProp) {//A m_scadaToFields[scadaProp].removeAll(fieldProp);//C if (m_scadaToFields[scadaProp].isEmpty()) {//C m_propertyToScadas[deviceProp].removeAll(scadaProp);//B m_scadaToDevice.remove(scadaProp);//A m_scadaToFields.remove(scadaProp);//C m_scadaToKey.remove(scadaProp); } } m_fieldToMember.remove(fieldProp); } } // ====================== 主管理器类(CustomDevicesManager)====================== CustomDevicesManager::CustomDevicesManager(QObject* parent) : QtAbstractPropertyManager(parent) , d_ptr(new CustomDevicesManagerPrivate) { Q_D(CustomDevicesManager); d->q_ptr = this; // sub String manager. d->m_stringPropertyManager = new QtStringPropertyManager(this); // sub manager signals. connect(d->m_stringPropertyManager, &QtStringPropertyManager::valueChanged, this, [d](QtProperty* prop, const QString& val) { d->slotStringChanged(prop, val); }); connect(d->m_stringPropertyManager, &QtStringPropertyManager::propertyDestroyed, this, [d](QtProperty* prop) { d->slotPropertyDestroyed(prop); }); // group manager. d_ptr->m_groupPropertyManager = new QtGroupPropertyManager(this); // destroyed. connect(d->m_groupPropertyManager, &QtGroupPropertyManager::propertyDestroyed, this, [d](QtProperty* prop) { d->slotPropertyDestroyed(prop); }); } CustomDevicesManager::~CustomDevicesManager() { clear(); } QtStringPropertyManager* CustomDevicesManager::subStringPropertyManager() const { Q_D(const CustomDevicesManager); return d->m_stringPropertyManager; } Graphics::ScadaHash CustomDevicesManager::value(const QtProperty* property) const { Q_D(const CustomDevicesManager); return d->m_values.value(property, Graphics::ScadaHash()); } bool CustomDevicesManager::isManagedProperty(const QtProperty *property) const { Q_D(const CustomDevicesManager); return d->m_values.contains(property); // 直接检查核心映射是否包含该属性 } void CustomDevicesManager::setValue(QtProperty* property, const Graphics::ScadaHash& val) { Q_D(CustomDevicesManager); const auto it = d->m_values.find(property); if (it == d->m_values.end()) return; if (it.value() == val) return; it.value() = val; QList<QtProperty*>& scadaProps = d->m_propertyToScadas[property]; // 关键1:清理无效属性(仅保留val中存在的scadaProp) QList<QtProperty*> validScadaProps; for (QtProperty* prop : scadaProps) { Graphics::ScadaKey key = d->m_scadaToKey.value(prop); if (val.contains(key)) { validScadaProps.append(prop); } else { QList<QtProperty*>& fieldProps = d->m_scadaToFields[prop]; for (QtProperty* fieldProp : fieldProps) { d->m_fieldToScada.remove(fieldProp); d->m_fieldToMember.remove(fieldProp); delete fieldProp; } d->m_scadaToDevice.remove(prop); d->m_scadaToFields.remove(prop); d->m_scadaToKey.remove(prop); delete prop; } } scadaProps = validScadaProps; // 关键2:用QHash替代QMap(无需ScadaKey重载<运算符) QHash<Graphics::ScadaKey, QtProperty*> scadaKeyToProp; for (QtProperty* prop : scadaProps) { scadaKeyToProp.insert(d->m_scadaToKey.value(prop), prop); } // 遍历val,更新或创建属性 int scadaIdx = 0; for (auto hashIt = val.begin(); hashIt != val.end(); ++hashIt) { Graphics::ScadaKey scadaKey = hashIt.key(); Graphics::ScadaPtr scadaPtr = hashIt.value(); QtProperty* scadaProp = scadaKeyToProp.take(scadaKey); // 优先复用 if (!scadaProp) { // 无复用才创建 scadaProp = d->m_groupPropertyManager->addProperty(); scadaProps.append(scadaProp); d->m_scadaToDevice[scadaProp] = property; d->m_scadaToKey[scadaProp] = scadaKey; property->addSubProperty(scadaProp); } // 更新属性名和tooltip scadaProp->setPropertyName(QString("参数%1").arg(scadaIdx + 1)); scadaProp->setToolTip(QString("code: %1, tableName: %2").arg(scadaKey.code).arg(scadaKey.tableName)); // 提取元属性(不变) QList<QPair<QString, QString>> metaProps; if (scadaPtr) { const QMetaObject* metaObj = scadaPtr->metaObject(); for (int i = metaObj->propertyOffset(); i < metaObj->propertyCount(); ++i) { QMetaProperty metaProp = metaObj->property(i); if (!metaProp.isReadable() || !metaProp.isWritable()) continue; QString memberName = metaProp.name(); QString memberLabel = memberName; for (int j = 1; j < memberLabel.size(); ++j) { if (memberLabel[j].isUpper() && memberLabel[j-1].isLower()) { memberLabel.insert(j, ' '); j++; } } memberLabel[0] = memberLabel[0].toUpper(); metaProps.append({memberName, memberLabel}); } } // 处理子属性字段(不变) QList<QtProperty*>& fieldProps = d->m_scadaToFields[scadaProp]; while (fieldProps.size() > metaProps.size()) { QtProperty* delFieldProp = fieldProps.takeLast(); d->m_fieldToScada.remove(delFieldProp); d->m_fieldToMember.remove(delFieldProp); delete delFieldProp; } // 动态创建/更新子字段(不变) for (int fieldIdx = 0; fieldIdx < metaProps.size(); ++fieldIdx) { QString memberName = metaProps[fieldIdx].first; QString memberLabel = metaProps[fieldIdx].second; QtProperty* fieldProp = nullptr; if (fieldIdx < fieldProps.size()) { fieldProp = fieldProps[fieldIdx]; fieldProp->setPropertyName(memberLabel); } else { fieldProp = d->m_stringPropertyManager->addProperty(); fieldProp->setPropertyName(memberLabel); fieldProps.append(fieldProp); d->m_fieldToScada[fieldProp] = scadaProp; d->m_fieldToMember[fieldProp] = memberName; scadaProp->addSubProperty(fieldProp); } // 设置字段值(不变) QString fieldValue; if (scadaPtr) { const QMetaObject* metaObj = scadaPtr->metaObject(); int propIdx = metaObj->indexOfProperty(memberName.toUtf8().constData()); if (propIdx != -1) { QMetaProperty metaProp = metaObj->property(propIdx); QVariant propValue = metaProp.read(scadaPtr.get()); fieldValue = propValue.toString(); } } d->m_stringPropertyManager->setValue(fieldProp, fieldValue); } scadaIdx++; } emit propertyChanged(property); emit valueChanged(property, val); // Q_D(CustomDevicesManager); // const auto it = d->m_values.find(property); // if (it == d->m_values.end()) return; // if (it.value() == val) return; // it.value() = val; // QList<QtProperty*>& scadaProps = d->m_propertyToScadas[property]; // // remove the redundant property. // while (scadaProps.size() > val.size()) { // QtProperty* delScadaProp = scadaProps.takeLast(); // QList<QtProperty*>& fieldProps = d->m_scadaToFields[delScadaProp]; // for (QtProperty* fieldProp : fieldProps) { // d->m_fieldToScada.remove(fieldProp); // d->m_fieldToMember.remove(fieldProp); // delete fieldProp; // } // d->m_scadaToDevice.remove(delScadaProp); // d->m_scadaToFields.remove(delScadaProp); // d->m_scadaToKey.remove(delScadaProp); // delete delScadaProp; // } // // iterate the val // int scadaIdx = 0; // for (auto hashIt = val.begin(); hashIt != val.end(); ++hashIt) { // Graphics::ScadaKey scadaKey = hashIt.key(); // Graphics::ScadaPtr scadaPtr = hashIt.value(); // QtProperty* scadaProp = nullptr; // // 复用或创建复合属性(参数X) // if (scadaIdx < scadaProps.size()) { // scadaProp = scadaProps[scadaIdx]; // scadaProp->setPropertyName(QString("参数%1").arg(scadaIdx + 1)); // scadaProp->setToolTip(QString("code: %1, tableName: %2").arg(scadaKey.code).arg(scadaKey.tableName)); // } else { // scadaProp = d->m_groupPropertyManager->addProperty(); // scadaProp->setPropertyName(QString("参数%1").arg(scadaIdx + 1)); // scadaProp->setToolTip(QString("code: %1, tableName: %2").arg(scadaKey.code).arg(scadaKey.tableName)); // scadaProps.append(scadaProp); // d->m_scadaToDevice[scadaProp] = property; // d->m_scadaToKey[scadaProp] = scadaKey; // property->addSubProperty(scadaProp); // } // // 3. 从 ScadaPtr 元对象中提取元属性(动态替代固定memberList) // QList<QPair<QString, QString>> metaProps; // <属性名(memberName), 显示标签> // if (scadaPtr) { // const QMetaObject* metaObj = scadaPtr->metaObject(); // // 遍历所有可读写的属性(跳过父类继承的属性,仅保留自身属性) // for (int i = metaObj->propertyOffset(); i < metaObj->propertyCount(); ++i) { // QMetaProperty metaProp = metaObj->property(i); // // 关键修正:删除!metaProp.isUser(),避免过滤所有属性 // if (!metaProp.isReadable() || !metaProp.isWritable()) continue; // QString memberName = metaProp.name(); // 属性名(如"code") // // 核心:属性名自动转人性化显示(兼容所有Qt版本) // QString memberLabel = memberName; // for (int j = 1; j < memberLabel.size(); ++j) { // // 驼峰命名分隔(如"tableName"→"Table Name") // if (memberLabel[j].isUpper() && memberLabel[j-1].isLower()) { // memberLabel.insert(j, ' '); // j++; // 跳过新增的空格,避免连续分隔 // } // } // // 首字母大写(如"table name"→"Table Name") // memberLabel[0] = memberLabel[0].toUpper(); // metaProps.append({memberName, memberLabel}); // } // } // // 4. 处理元属性字段(LineEdit) // QList<QtProperty*>& fieldProps = d->m_scadaToFields[scadaProp]; // while (fieldProps.size() > metaProps.size()) { // QtProperty* delFieldProp = fieldProps.takeLast(); // d->m_fieldToScada.remove(delFieldProp); // d->m_fieldToMember.remove(delFieldProp); // delete delFieldProp; // } // // 5. 动态创建/更新字段(无固定字符判断) // for (int fieldIdx = 0; fieldIdx < metaProps.size(); ++fieldIdx) { // QString memberName = metaProps[fieldIdx].first; // QString memberLabel = metaProps[fieldIdx].second; // QtProperty* fieldProp = nullptr; // if (fieldIdx < fieldProps.size()) { // fieldProp = fieldProps[fieldIdx]; // fieldProp->setPropertyName(memberLabel); // } else { // fieldProp = d->m_stringPropertyManager->addProperty(); // fieldProp->setPropertyName(memberLabel); // fieldProps.append(fieldProp); // d->m_fieldToScada[fieldProp] = scadaProp; // d->m_fieldToMember[fieldProp] = memberName; // scadaProp->addSubProperty(fieldProp); // 子项关联到复合属性 // } // // 动态设置字段值(通过元属性读取,替代固定if判断) // QString fieldValue; // if (scadaPtr) { // const QMetaObject* metaObj = scadaPtr->metaObject(); // int propIdx = metaObj->indexOfProperty(memberName.toUtf8().constData()); // if (propIdx != -1) { // QMetaProperty metaProp = metaObj->property(propIdx); // QVariant propValue = metaProp.read(scadaPtr.get()); // fieldValue = propValue.toString(); // } // } // d->m_stringPropertyManager->setValue(fieldProp, fieldValue); // } // scadaIdx++; // } // emit propertyChanged(property); // emit valueChanged(property, val); // Q_D(CustomDevicesManager); // const auto it = d->m_values.find(property); // if (it == d->m_values.end()) return; // if (it.value() == val) return; // // 更新核心数据(ScadaHash) // it.value() = val; // // 动态创建/更新 Scada 复合属性(参数1、参数2...) // QList<QtProperty*>& scadaProps = d->m_propertyToScadas[property]; // // 1. 移除多余的复合属性 // while (scadaProps.size() > val.size()) { // QtProperty* delScadaProp = scadaProps.takeLast(); // QList<QtProperty*>& fieldProps = d->m_scadaToFields[delScadaProp]; // for (QtProperty* fieldProp : fieldProps) { // d->m_fieldToScada.remove(fieldProp); // d->m_fieldToMember.remove(fieldProp); // delete fieldProp; // } // d->m_scadaToDevice.remove(delScadaProp); // d->m_scadaToFields.remove(delScadaProp); // d->m_scadaToKey.remove(delScadaProp); // delete delScadaProp; // } // // 2. 定义 Scada 元属性(名称+显示标签) // QList<QPair<QString, QString>> memberList; // memberList.append(QPair<QString, QString>("code", "代码")); // memberList.append(QPair<QString, QString>("domain", "域名")); // memberList.append(QPair<QString, QString>("type", "类型")); // memberList.append(QPair<QString, QString>("substation", "厂站")); // memberList.append(QPair<QString, QString>("tableName", "参数库表名")); // memberList.append(QPair<QString, QString>("realTableName", "实时库表名")); // memberList.append(QPair<QString, QString>("deviceId", "设备ID")); // memberList.append(QPair<QString, QString>("deviceCode", "设备代码")); // // 3. 遍历 ScadaHash,创建/更新复合属性和字段 // int scadaIdx = 0; // for (auto hashIt = val.begin(); hashIt != val.end(); ++hashIt) { // Graphics::ScadaKey scadaKey = hashIt.key(); // Graphics::ScadaPtr scadaPtr = hashIt.value(); // QtProperty* scadaProp = nullptr; // // 复用或创建复合属性(参数X) // if (scadaIdx < scadaProps.size()) { // scadaProp = scadaProps[scadaIdx]; // // 核心:复合属性的“值列”显示 ScadaKey(code + tableName) // scadaProp->setPropertyName(QString("参数%1").arg(scadaIdx + 1)); // scadaProp->setToolTip(QString("code: %1, tableName: %2").arg(scadaKey.code).arg(scadaKey.tableName)); // } else { // scadaProp = d->m_groupPropertyManager->addProperty(); // scadaProp->setPropertyName(QString("参数%1").arg(scadaIdx + 1)); // scadaProp->setToolTip(QString("code: %1, tableName: %2").arg(scadaKey.code).arg(scadaKey.tableName)); // scadaProps.append(scadaProp); // d->m_scadaToDevice[scadaProp] = property; // d->m_scadaToKey[scadaProp] = scadaKey; // property->addSubProperty(scadaProp); // } // // 4. 处理元属性字段(LineEdit) // QList<QtProperty*>& fieldProps = d->m_scadaToFields[scadaProp]; // while (fieldProps.size() > memberList.size()) { // QtProperty* delFieldProp = fieldProps.takeLast(); // d->m_fieldToScada.remove(delFieldProp); // d->m_fieldToMember.remove(delFieldProp); // delete delFieldProp; // } // // 创建/更新字段 // for (int fieldIdx = 0; fieldIdx < memberList.size(); ++fieldIdx) { // QString memberName = memberList[fieldIdx].first; // QString memberLabel = memberList[fieldIdx].second; // QtProperty* fieldProp = nullptr; // if (fieldIdx < fieldProps.size()) { // fieldProp = fieldProps[fieldIdx]; // fieldProp->setPropertyName(memberLabel); // } else { // fieldProp = d->m_stringPropertyManager->addProperty(); // fieldProp->setPropertyName(memberLabel); // fieldProps.append(fieldProp); // d->m_fieldToScada[fieldProp] = scadaProp; // d->m_fieldToMember[fieldProp] = memberName; // scadaProp->addSubProperty(fieldProp); // } // // 设置字段值(从 ScadaPtr 读取) // QString fieldValue; // if (scadaPtr) { // if (memberName == "code") fieldValue = scadaPtr->getCode(); // else if (memberName == "domain") fieldValue = scadaPtr->getDomain(); // else if (memberName == "type") fieldValue = QString::number(scadaPtr->getType()); // else if (memberName == "substation") fieldValue = scadaPtr->getSubstation(); // else if (memberName == "tableName") fieldValue = scadaPtr->getTableName(); // else if (memberName == "realTableName") fieldValue = scadaPtr->getRealTableName(); // else if (memberName == "deviceId") fieldValue = scadaPtr->getDeviceId(); // else if (memberName == "deviceCode") fieldValue = scadaPtr->getDeviceCode(); // } // d->m_stringPropertyManager->setValue(fieldProp, fieldValue); // } // scadaIdx++; // } // emit propertyChanged(property); // emit valueChanged(property, val); } QString CustomDevicesManager::valueText(const QtProperty* property) const { Q_D(const CustomDevicesManager); const auto it = d->m_values.constFind(property); if (it == d->m_values.constEnd()) return QString(); // 主属性显示:ScadaHash 数据量概览 const Graphics::ScadaHash& scadaHash = it.value(); int totalFieldCount = scadaHash.size() * 8; // 每个 Scada 8 个字段 return QString("数据点配置(%1个数据点,共%2个字段)").arg(scadaHash.size()).arg(totalFieldCount); } void CustomDevicesManager::initializeProperty(QtProperty* property) { Q_D(CustomDevicesManager); if (!d->m_values.contains(property)) { d->m_values[property] = Graphics::ScadaHash(); d->m_propertyToScadas[property] = QList<QtProperty*>(); } } void CustomDevicesManager::uninitializeProperty(QtProperty* property) { Q_D(CustomDevicesManager); QList<QtProperty*>& scadaProps = d->m_propertyToScadas[property]; for (QtProperty* scadaProp : scadaProps) { QList<QtProperty*>& fieldProps = d->m_scadaToFields[scadaProp]; for (QtProperty* fieldProp : fieldProps) { d->m_fieldToScada.remove(fieldProp); d->m_fieldToMember.remove(fieldProp); delete fieldProp; } d->m_scadaToDevice.remove(scadaProp); d->m_scadaToFields.remove(scadaProp); d->m_scadaToKey.remove(scadaProp); delete scadaProp; } d->m_propertyToScadas.remove(property); d->m_values.remove(property); } 我是用qtdesigner的源码相关的属性编辑器,加了自定义的属性,结果,我现在每次setValue都会导致私有类定义的数据结构里面的个数都增加1
12-04
我这里要优化这个地形系统,现在我需要优化交互精度、视觉效果,挖掘时地形确实会变形凹陷,但深度不太合理,铲斗明明只是挖了凹陷的1/2深度却凹陷了这么多,希望让挖掘深度的精度高一点使土壤变形可以匹配铲斗形状的挖掘以下是代码using System.Collections.Generic; using Unity.Collections; using Unity.Mathematics; using UnityEngine; using UnityEngine.Rendering; public class GenTestForDigging : MonoBehaviour { [Header(“Terraforming”)] [Range(1f, 20f)] public float fillRadius = 5f; // 填充半径 [Range(1f, 20f)] public float digRadius = 5f; // 挖掘半径 [Header("Init Settings")] public int numChunks = 4; public int numPointsPerAxis = 10; public float boundsSize = 10; public float isoLevel = 0f; public bool useFlatShading; public float noiseScale; public float noiseHeightMultiplier; public bool blurMap; public int blurRadius = 3; [Header("挖掘区域可视化")] public Color digZoneColor = new Color(0, 1, 0, 0.2f); // 区域填充色 public Color digZoneOutlineColor = new Color(0, 1, 0, 0.8f); // 虚线边框色 public float outlineThickness = 2f; // 边框厚度(辅助) [Header("References")] public ComputeShader meshCompute; public ComputeShader densityCompute; public ComputeShader blurCompute; public ComputeShader editCompute; public Material material; [Header("可挖掘区域设置")] public List<DigZone> digZones = new List<DigZone>(); public bool restrictToZones = true; // 是否是否限制在指定的区域内挖掘 [Header("性能优化")] public float updateInterval = 0.1f; // 地形更新间隔(避免每帧更新) private float lastUpdateTime; // 记录上次更新时间 public bool useIncrementalUpdate = true; // 启用增量更新(分帧处理区块) public int maxChunksPerUpdate = 2; // 每次更新最多处理2个区块(可调整) private Queue<Chunk> chunksToUpdate = new Queue<Chunk>(); // 待更新区块队列 [Header("土堆效果参数")] public float pileSmoothness = 50f; // 土堆平滑度(值越高越顺滑) public float pileDensity = 0.8f; // 土堆密度(值越高越紧凑) public float pileRandomness = 0.2f; // 土堆随机度(避免规则形状) public float pileFlatTopRatio = 0.4f; // 圆锥平顶比例(0.4=顶部40%是平的) public int pileSampleSteps = 8; // 采样步数(原5→8,提升精细度) // Private ComputeBuffer triangleBuffer; ComputeBuffer triCountBuffer; [HideInInspector] public RenderTexture rawDensityTexture; [HideInInspector] public RenderTexture processedDensityTexture; Chunk[] chunks; VertexData[] vertexDataArray; int totalVerts; // Stopwatches System.Diagnostics.Stopwatch timer_fetchVertexData; System.Diagnostics.Stopwatch timer_processVertexData; RenderTexture originalMap; void Start() { InitTextures(); CreateBuffers(); CreateChunks(); var sw = System.Diagnostics.Stopwatch.StartNew(); GenerateAllChunks(); Debug.Log("Generation Time: " + sw.ElapsedMilliseconds + " ms"); ComputeHelper.CreateRenderTexture3D(ref originalMap, processedDensityTexture); ComputeHelper.CopyRenderTexture3D(processedDensityTexture, originalMap); // 初始化更新队列 chunksToUpdate = new Queue<Chunk>(); lastUpdateTime = Time.time; // 初始化更新时间,避免首次更新延迟过长 } void InitTextures() { // Explanation of texture size: // Each pixel maps to one point. // Each chunk has "numPointsPerAxis" points along each axis // The last points of each chunk overlap in space with the first points of the next chunk // Therefore we need one fewer pixel than points for each added chunk int size = numChunks * (numPointsPerAxis - 1) + 1; Create3DTexture(ref rawDensityTexture, size, "Raw Density Texture"); Create3DTexture(ref processedDensityTexture, size, "Processed Density Texture"); if (!blurMap) { processedDensityTexture = rawDensityTexture; } // Set textures on compute shaders densityCompute.SetTexture(0, "DensityTexture", rawDensityTexture); editCompute.SetTexture(0, "EditTexture", rawDensityTexture); blurCompute.SetTexture(0, "Source", rawDensityTexture); blurCompute.SetTexture(0, "Result", processedDensityTexture); meshCompute.SetTexture(0, "DensityTexture", (blurCompute) ? processedDensityTexture : rawDensityTexture); } void GenerateAllChunks() { timer_fetchVertexData = new System.Diagnostics.Stopwatch(); timer_processVertexData = new System.Diagnostics.Stopwatch(); totalVerts = 0; ComputeDensity(); // 修复:初始生成时强制关闭增量更新,直接生成所有Chunk bool tempIncremental = useIncrementalUpdate; useIncrementalUpdate = false; // 临时关闭增量更新 foreach (var chunk in chunks) { GenerateChunk(chunk, false); // 直接生成,不入队 } useIncrementalUpdate = tempIncremental; // 恢复增量更新设置 Debug.Log("Total verts " + totalVerts); Debug.Log("Fetch vertex data: " + timer_fetchVertexData.ElapsedMilliseconds + " ms"); Debug.Log("Process vertex data: " + timer_processVertexData.ElapsedMilliseconds + " ms"); } void ComputeDensity() { // 获取纹理大小 int textureSize = rawDensityTexture.width; densityCompute.SetInt("textureSize", textureSize); densityCompute.SetFloat("planetSize", boundsSize); // 添加立方体参数 densityCompute.SetBool("generateCube", true); densityCompute.SetFloat("cubeSize", boundsSize * 0.3f); // 立方体大小为边界的80% // 仍然需要这些参数以避免错误 densityCompute.SetFloat("noiseHeightMultiplier", noiseHeightMultiplier); densityCompute.SetFloat("noiseScale", noiseScale); ComputeHelper.Dispatch(densityCompute, textureSize, textureSize, textureSize); ProcessDensityMap(); } [System.Serializable] public class DigZone { public string zoneName; public Vector3 center; public Vector3 size; public bool isActive = true; public LayerMask allowedLayers; // 允许挖掘的图层 [Tooltip("是否显示虚线边框")] public bool showOutline = true; [Tooltip("虚线间隔比例")] public float dashSpacing = 0.1f; } void ProcessDensityMap() { if (blurMap) { int size = rawDensityTexture.width; blurCompute.SetInts("brushCentre", 0, 0, 0); blurCompute.SetInt("blurRadius", blurRadius); blurCompute.SetInt("textureSize", rawDensityTexture.width); ComputeHelper.Dispatch(blurCompute, size, size, size); } } void OnDrawGizmosSelected() { // 绘制挖掘区域,带虚线边框 foreach (var zone in digZones) { if (!zone.isActive) continue; // 1. 绘制区域填充色 Gizmos.color = digZoneColor; Gizmos.DrawCube(zone.center, zone.size); // 2. 绘制虚线边框 if (zone.showOutline) { Gizmos.color = digZoneOutlineColor; DrawDashedCube(zone.center, zone.size, zone.dashSpacing); } } } // 绘制虚线立方体 void DrawDashedCube(Vector3 center, Vector3 size, float dashSpacing) { Vector3 halfSize = size / 2; Vector3[] corners = new Vector3[8] { center + new Vector3(halfSize.x, halfSize.y, halfSize.z), center + new Vector3(-halfSize.x, halfSize.y, halfSize.z), center + new Vector3(-halfSize.x, halfSize.y, -halfSize.z), center + new Vector3(halfSize.x, halfSize.y, -halfSize.z), center + new Vector3(halfSize.x, -halfSize.y, halfSize.z), center + new Vector3(-halfSize.x, -halfSize.y, halfSize.z), center + new Vector3(-halfSize.x, -halfSize.y, -halfSize.z), center + new Vector3(halfSize.x, -halfSize.y, -halfSize.z) }; // 顶部四条边 DrawDashedLine(corners[0], corners[1], dashSpacing); DrawDashedLine(corners[1], corners[2], dashSpacing); DrawDashedLine(corners[2], corners[3], dashSpacing); DrawDashedLine(corners[3], corners[0], dashSpacing); // 底部四条边 DrawDashedLine(corners[4], corners[5], dashSpacing); DrawDashedLine(corners[5], corners[6], dashSpacing); DrawDashedLine(corners[6], corners[7], dashSpacing); DrawDashedLine(corners[7], corners[4], dashSpacing); // 连接上下的四条边 DrawDashedLine(corners[0], corners[4], dashSpacing); DrawDashedLine(corners[1], corners[5], dashSpacing); DrawDashedLine(corners[2], corners[6], dashSpacing); DrawDashedLine(corners[3], corners[7], dashSpacing); } // 绘制单条虚线 void DrawDashedLine(Vector3 start, Vector3 end, float dashSpacing) { Vector3 direction = end - start; float length = direction.magnitude; direction.Normalize(); float distance = 0; bool drawSegment = true; while (distance < length) { float segmentLength = Mathf.Min(dashSpacing, length - distance); Vector3 segmentEnd = start + direction * (distance + segmentLength); if (drawSegment) { Gizmos.DrawLine(start + direction * distance, segmentEnd); } distance += segmentLength; drawSegment = !drawSegment; } } void GenerateChunk(Chunk chunk, bool addToQueue = true) { // 如果启用增量更新,先入队,不立即生成 if (useIncrementalUpdate && addToQueue) { if (!chunksToUpdate.Contains(chunk)) { chunksToUpdate.Enqueue(chunk); } return; } // 原有生成逻辑(不变) int numVoxelsPerAxis = numPointsPerAxis - 1; int marchKernel = 0; meshCompute.SetInt("textureSize", processedDensityTexture.width); meshCompute.SetInt("numPointsPerAxis", numPointsPerAxis); meshCompute.SetFloat("isoLevel", isoLevel); meshCompute.SetFloat("planetSize", boundsSize); triangleBuffer.SetCounterValue(0); meshCompute.SetBuffer(marchKernel, "triangles", triangleBuffer); Vector3 chunkCoord = (Vector3)chunk.id * (numPointsPerAxis - 1); meshCompute.SetVector("chunkCoord", chunkCoord); ComputeHelper.Dispatch(meshCompute, numVoxelsPerAxis, numVoxelsPerAxis, numVoxelsPerAxis, marchKernel); int[] vertexCountData = new int[1]; triCountBuffer.SetData(vertexCountData); ComputeBuffer.CopyCount(triangleBuffer, triCountBuffer, 0); timer_fetchVertexData.Start(); triCountBuffer.GetData(vertexCountData); int numVertices = vertexCountData[0] * 3; if (numVertices > 0 && numVertices <= vertexDataArray.Length) { triangleBuffer.GetData(vertexDataArray, 0, 0, numVertices); timer_fetchVertexData.Stop(); timer_processVertexData.Start(); chunk.CreateMesh(vertexDataArray, numVertices, useFlatShading); timer_processVertexData.Stop(); } } void Update() { // TODO: move somewhere more sensible material.SetTexture("DensityTex", originalMap); // material.SetFloat("oceanRadius", FindObjectOfType<Water>().radius); material.SetFloat("planetBoundsSize", boundsSize); // 新按间隔处理待更新区块 if (useIncrementalUpdate && Time.time - lastUpdateTime > updateInterval) { lastUpdateTime = Time.time; ProcessQueuedChunks(); } } // 处理队列中的区块更新(分帧执行,避免卡顿) void ProcessQueuedChunks() { int processed = 0; while (chunksToUpdate.Count > 0 && processed < maxChunksPerUpdate) { Chunk chunk = chunksToUpdate.Dequeue(); GenerateChunk(chunk, false); // 直接生成,不入队 processed++; } } public bool IsInDigZone(Vector3 position) { // 修复:如果没有配置任何可挖掘区域,直接返回true if (digZones.Count == 0) return true; if (!restrictToZones) return true; foreach (var zone in digZones) { if (!zone.isActive) continue; Vector3 localPos = position - zone.center; if (Mathf.Abs(localPos.x) < zone.size.x / 2 && Mathf.Abs(localPos.y) < zone.size.y / 2 && Mathf.Abs(localPos.z) < zone.size.z / 2) { return true; } } return false; } void CreateBuffers() { int numPoints = numPointsPerAxis * numPointsPerAxis * numPointsPerAxis; int numVoxelsPerAxis = numPointsPerAxis - 1; int numVoxels = numVoxelsPerAxis * numVoxelsPerAxis * numVoxelsPerAxis; int maxTriangleCount = numVoxels * 5; int maxVertexCount = maxTriangleCount * 3; triCountBuffer = new ComputeBuffer(1, sizeof(int), ComputeBufferType.Raw); triangleBuffer = new ComputeBuffer(maxVertexCount, ComputeHelper.GetStride<VertexData>(), ComputeBufferType.Append); vertexDataArray = new VertexData[maxVertexCount]; } void ReleaseBuffers() { ComputeHelper.Release(triangleBuffer, triCountBuffer); } void OnDestroy() { ReleaseBuffers(); foreach (Chunk chunk in chunks) { chunk.Release(); } } void CreateChunks() { chunks = new Chunk[numChunks * numChunks * numChunks]; float chunkSize = (boundsSize) / numChunks; int i = 0; for (int y = 0; y < numChunks; y++) { for (int x = 0; x < numChunks; x++) { for (int z = 0; z < numChunks; z++) { Vector3Int coord = new Vector3Int(x, y, z); float posX = (-(numChunks - 1f) / 2 + x) * chunkSize; float posY = (-(numChunks - 1f) / 2 + y) * chunkSize; float posZ = (-(numChunks - 1f) / 2 + z) * chunkSize; Vector3 centre = new Vector3(posX, posY, posZ); GameObject meshHolder = new GameObject($"Chunk ({x}, {y}, {z})"); meshHolder.transform.parent = transform; meshHolder.layer = gameObject.layer; Chunk chunk = new Chunk(coord, centre, chunkSize, numPointsPerAxis, meshHolder); chunk.SetMaterial(material); chunks[i] = chunk; i++; } } } } #region 老Terraform方法 public void Terraform(Vector3 point, float weight, float radius) { int editTextureSize = rawDensityTexture.width; float editPixelWorldSize = boundsSize / editTextureSize; int editRadius = Mathf.CeilToInt(radius / editPixelWorldSize); //Debug.Log(editPixelWorldSize + " " + editRadius); float tx = Mathf.Clamp01((point.x + boundsSize / 2) / boundsSize); float ty = Mathf.Clamp01((point.y + boundsSize / 2) / boundsSize); float tz = Mathf.Clamp01((point.z + boundsSize / 2) / boundsSize); int editX = Mathf.RoundToInt(tx * (editTextureSize - 1)); int editY = Mathf.RoundToInt(ty * (editTextureSize - 1)); int editZ = Mathf.RoundToInt(tz * (editTextureSize - 1)); editCompute.SetFloat("weight", weight); editCompute.SetFloat("deltaTime", Time.deltaTime); editCompute.SetInts("brushCentre", editX, editY, editZ); editCompute.SetInt("brushRadius", editRadius); // 区分挖掘/填充参数 if (weight > 0) // 挖掘 { editCompute.SetFloat("smoothness", 20f); editCompute.SetFloat("brushStrength", 200f); } else // 填充(土堆) { editCompute.SetFloat("smoothness", pileSmoothness); editCompute.SetFloat("brushStrength", 200f * pileDensity); editCompute.SetFloat("randomness", pileRandomness); // 传递随机性 } editCompute.SetInt("size", editTextureSize); int dispatchSize = Mathf.Max(1, editRadius * 2); ComputeHelper.Dispatch(editCompute, dispatchSize, dispatchSize, dispatchSize); //ProcessDensityMap(); int size = rawDensityTexture.width; if (blurMap) { blurCompute.SetInt("textureSize", rawDensityTexture.width); blurCompute.SetInts("brushCentre", editX - blurRadius, editY - blurRadius, editZ - blurRadius); blurCompute.SetInt("blurRadius", blurRadius); blurCompute.SetInt("brushRadius", editRadius); int k = (editRadius + blurRadius) * 2; ComputeHelper.Dispatch(blurCompute, k, k, k); // 仅处理模糊+编辑范围 //填充时二次模糊,解决土堆棱角 if (weight < 0) // 仅填充(倒土)时执行二次模糊 { // 扩大模糊半径+1,平滑土堆边缘,减少棱角 blurCompute.SetInt("blurRadius", blurRadius + 1); ComputeHelper.Dispatch(blurCompute, k, k, k); // 恢复原模糊半径,避免影响后续操作 blurCompute.SetInt("blurRadius", blurRadius); } } //ComputeHelper.CopyRenderTexture3D(originalMap, processedDensityTexture); float worldRadius = (editRadius + 1 + ((blurMap) ? blurRadius : 0)) * editPixelWorldSize; for (int i = 0; i < chunks.Length; i++) { Chunk chunk = chunks[i]; if (MathUtility.SphereIntersectsBox(point, worldRadius, chunk.centre, Vector3.one * chunk.size)) { chunk.terra = true; GenerateChunk(chunk); } } } // 添加新的重载方法,支持椭圆形编辑和旋转 public void Terraform(Vector3 point, float weight, Vector3 dimensions, Vector3 rotationAngles) { int editTextureSize = rawDensityTexture.width; float editPixelWorldSize = boundsSize / editTextureSize; // 计算每个轴上的编辑半径(以像素为单位) int editRadiusX = Mathf.CeilToInt(dimensions.x / 2 / editPixelWorldSize); int editRadiusY = Mathf.CeilToInt(dimensions.y / 2 / editPixelWorldSize); int editRadiusZ = Mathf.CeilToInt(dimensions.z / 2 / editPixelWorldSize); int editRadius = Mathf.Max(editRadiusX, editRadiusY, editRadiusZ); float tx = Mathf.Clamp01((point.x + boundsSize / 2) / boundsSize); float ty = Mathf.Clamp01((point.y + boundsSize / 2) / boundsSize); float tz = Mathf.Clamp01((point.z + boundsSize / 2) / boundsSize); int editX = Mathf.RoundToInt(tx * (editTextureSize - 1)); int editY = Mathf.RoundToInt(ty * (editTextureSize - 1)); int editZ = Mathf.RoundToInt(tz * (editTextureSize - 1)); // 创建旋转矩阵(欧拉角转四元数再转矩阵) Quaternion rotation = Quaternion.Euler(rotationAngles); Matrix4x4 rotationMatrix = Matrix4x4.Rotate(rotation); editCompute.SetFloat("weight", weight); editCompute.SetFloat("deltaTime", Time.deltaTime); editCompute.SetInts("brushCentre", editX, editY, editZ); editCompute.SetInt("brushRadius", editRadius); editCompute.SetVector("brushDimensions", new Vector4( dimensions.x / editPixelWorldSize, dimensions.y / editPixelWorldSize, dimensions.z / editPixelWorldSize, 0)); editCompute.SetMatrix("brushMatrix", rotationMatrix); editCompute.SetFloat("smoothness", 20f); // 控制边缘软硬 editCompute.SetFloat("brushStrength", 200f); // 控制挖掘强度 editCompute.SetInt("size", editTextureSize); ComputeHelper.Dispatch(editCompute, editTextureSize, editTextureSize, editTextureSize); if (blurMap) { blurCompute.SetInt("textureSize", rawDensityTexture.width); blurCompute.SetInts("brushCentre", editX - blurRadius - editRadius, editY - blurRadius - editRadius, editZ - blurRadius - editRadius); blurCompute.SetInt("blurRadius", blurRadius); blurCompute.SetInt("brushRadius", editRadius); int k = (editRadius + blurRadius) * 2; ComputeHelper.Dispatch(blurCompute, k, k, k); } // 使用最大维度计算世界空间中的影响半径 float worldRadius = (editRadius + 1 + ((blurMap) ? blurRadius : 0)) * editPixelWorldSize; for (int i = 0; i < chunks.Length; i++) { Chunk chunk = chunks[i]; if (MathUtility.SphereIntersectsBox(point, worldRadius, chunk.centre, Vector3.one * chunk.size)) { chunk.terra = true; GenerateChunk(chunk); } } } #endregion void Create3DTexture(ref RenderTexture texture, int size, string name) { var format = UnityEngine.Experimental.Rendering.GraphicsFormat.R32_SFloat; if (texture == null || !texture.IsCreated() || texture.width != size || texture.height != size || texture.volumeDepth != size || texture.graphicsFormat != format) { //Debug.Log ("Create tex: update noise: " + updateNoise); if (texture != null) { texture.Release(); } const int numBitsInDepthBuffer = 0; texture = new RenderTexture(size, size, numBitsInDepthBuffer); texture.graphicsFormat = format; texture.volumeDepth = size; texture.enableRandomWrite = true; texture.dimension = UnityEngine.Rendering.TextureDimension.Tex3D; texture.Create(); } texture.wrapMode = TextureWrapMode.Repeat; texture.filterMode = FilterMode.Bilinear; texture.name = name; } }using System.Collections; using System.Collections.Generic; using UnityEngine; public class TerraformerForDigging : MonoBehaviour { // 添加挖掘和填充的形状控制 [Header(“填充参数”)] public Vector3 fillDimensions = new Vector3(3, 3, 3); // x:宽度, y:高度, z:深度 public Vector3 fillRotation = new Vector3(0, 0, 0); // x:绕X轴旋转, y:绕Y轴旋转, z:绕Z轴旋转 [Header("挖掘参数")] public Vector3 digDimensions = new Vector3(2, 2, 2); // x:宽度, y:高度, z:深度 public Vector3 digRotation = new Vector3(0, 0, 0); // x:绕X轴旋转, y:绕Y轴旋转, z:绕Z轴旋转 // 添加铲斗形状数据 [Header("铲斗形状设置")] public Vector3 bucketSize = new Vector3(1f, 0.5f, 0.8f); public float bucketCurvature = 0.3f; // 铲斗弧度 [Header("物理效果")] public float gravityInfluence = 0.2f; // 重力对土堆的影响(自然下沉) public float pileStability = 0.7f; // 土堆稳定性(值越高越不容易坍塌) [Header("倾倒优化")] public float groundCheckDistance = 2f; // 地面检测距离 public float minDumpHeight = 0.1f; // 最小倾倒高度(离地面太近不倒) public float maxDumpHeight = 1.5f; // 最大倾倒高度(太高不倒) public float dumpSmoothHeight = 0.3f; // 最佳倾倒高度(贴合地面) // 用于确定当前操作类型 private bool isDigging = false; private bool isFilling = false; public event System.Action onTerrainModified; public LayerMask terrainMask; // public float terraformRadius = 5; public float terraformSpeedNear = 0.1f; public float terraformSpeedFar = 0.25f; Vector3 rayDirection; Transform rayPoint; Transform cam; GenTestForDigging genTestForDigging; bool hasHit; Vector3 hitPoint; FirstPersonController firstPersonController; bool isTerraforming; Vector3 lastTerraformPointLocal; public GameObject bucketTip; public GameObject bucketTip2; void Start() { genTestForDigging = FindObjectOfType<GenTestForDigging>(); cam = Camera.main.transform; firstPersonController = FindObjectOfType<FirstPersonController>(); rayPoint = bucketTip2.transform; rayDirection = -Vector3.up; } void Update() { HandleTerraformEvent(); } public void HandleTerraformEvent(Transform customCam = null) { RaycastHit hit; hasHit = false; bool wasTerraformingLastFrame = isTerraforming; isTerraforming = false; int numIterations = 5; bool rayHitTerrain = false; Transform useCam = customCam != null ? customCam : cam; // 检测当前操作类型 // isDigging = Input.GetMouseButton(1); // isFilling = Input.GetMouseButton(0); // 获取当前使用的最大半径 float currentMaxRadius = Mathf.Max( Mathf.Max(fillDimensions.x, fillDimensions.y, fillDimensions.z) / 2, Mathf.Max(digDimensions.x, digDimensions.y, digDimensions.z) / 2); for (int i = 0; i < numIterations; i++) { float rayRadius = currentMaxRadius * Mathf.Lerp(0.01f, 1, i / (numIterations - 1f)); rayDirection = -bucketTip.transform.up; if (isDigging) { rayDirection = -bucketTip.transform.up; rayPoint = bucketTip.transform; // 调试信息 Debug.DrawRay(rayPoint.position, rayDirection * 100f, Color.red); Debug.Log("挖掘射线 - 方向:" + rayDirection + ",起点:" + rayPoint.position); } else if (isFilling) { rayDirection = -Vector3.up; rayPoint = bucketTip2.transform; // 调试信息 Debug.DrawRay(rayPoint.position, rayDirection * 100f, Color.blue); Debug.Log("填充射线 - 方向:" + rayDirection + ",起点:" + rayPoint.position); } if (Physics.SphereCast(rayPoint.position, rayRadius, rayDirection, out hit, 1000, terrainMask)) { lastTerraformPointLocal = MathUtility.WorldToLocalVector(useCam.rotation, hit.point); Terraform(hit.point); rayHitTerrain = true; break; } } if (!rayHitTerrain && wasTerraformingLastFrame) { Vector3 terraformPoint = MathUtility.LocalToWorldVector(useCam.rotation, lastTerraformPointLocal); Terraform(terraformPoint); } } public void ChangeDigging(bool isDigging) { this.isDigging = isDigging; } public void ChangeFilling(bool isFilling) { this.isFilling = isFilling; } void Terraform(Vector3 terraformPoint) { hasHit = true; hitPoint = terraformPoint; const float dstNear = 10; const float dstFar = 60; float dstFromCam = (terraformPoint - cam.position).magnitude; float weight01 = Mathf.InverseLerp(dstNear, dstFar, dstFromCam); float weight = Mathf.Lerp(terraformSpeedNear, terraformSpeedFar, weight01) * 3.3f; // 使用铲斗形状进行地形变形 if (isFilling) { isTerraforming = true; TerraformWithBucketShape(terraformPoint, -weight * 1f, bucketTip2.transform); float fillRadius = Mathf.Max(fillDimensions.x, fillDimensions.y, fillDimensions.z) / 2; } else if (isDigging) { isTerraforming = true; TerraformWithBucketShape(terraformPoint, weight * 1f, bucketTip.transform); } if (isTerraforming) { onTerrainModified?.Invoke(); } } void OnDrawGizmos() { if (hasHit) { Gizmos.color = isDigging ? Color.red : (isFilling ? Color.blue : Color.green); if (isDigging) { Quaternion digRot = Quaternion.Euler(digRotation); Gizmos.matrix = Matrix4x4.TRS(hitPoint, digRot, digDimensions); Gizmos.DrawWireSphere(Vector3.zero, 0.5f); } else if (isFilling) { Quaternion fillRot = Quaternion.Euler(fillRotation); Gizmos.matrix = Matrix4x4.TRS(hitPoint, fillRot, fillDimensions); Gizmos.DrawWireSphere(Vector3.zero, 0.5f); } else { Gizmos.DrawSphere(hitPoint, 0.25f); } Gizmos.matrix = Matrix4x4.identity; } } // 改进地形变形方法,使其贴合铲斗形状 void TerraformWithBucketShape(Vector3 terraformPoint, float weight, Transform bucketTransform) { if (!genTestForDigging.IsInDigZone(terraformPoint)) return; // 新增:地面高度检测 RaycastHit groundHit; Vector3 realGroundPoint = terraformPoint; if (Physics.Raycast(bucketTransform.position, Vector3.down, out groundHit, groundCheckDistance, terrainMask)) { realGroundPoint = groundHit.point; // 修正为实际地面位置 } else { // 超出地面检测范围,直接返回(避免空中倒土) //if (weight < 0) return; } // 新增:判断铲斗高度是否在合理范围 float dumpHeight = Vector3.Distance(bucketTransform.position, realGroundPoint); if (weight < 0 && (dumpHeight < minDumpHeight || dumpHeight > maxDumpHeight)) { //return; // 高度不合理,不倾倒 } Quaternion bucketRot = bucketTransform.rotation; Vector3 bucketPos = bucketTransform.position; // 新增:高度适配------最佳高度时权重最大,偏离则衰减 float heightFactor = 1f; //if (weight < 0) //{ //heightFactor = Mathf.InverseLerp(maxDumpHeight, minDumpHeight, Mathf.Abs(dumpHeight - dumpSmoothHeight)); //} Vector3 scaledDimensions = new Vector3( bucketSize.x, bucketSize.y, bucketSize.z ); // 填充时:修正为地面位置 + 高度衰减权重 if (weight < 0) { Vector3 gravityAdjustedPoint = realGroundPoint; // 用实际地面位置 gravityAdjustedPoint.y += 0.05f; // 轻微抬升,避免埋入地下 genTestForDigging.Terraform( gravityAdjustedPoint, weight * 2f,//* pileStability * heightFactor, // 叠加高度衰减 scaledDimensions, bucketRot.eulerAngles ); } else { genTestForDigging.Terraform( terraformPoint, weight, scaledDimensions, bucketRot.eulerAngles ); } ApplyBucketCurvature(bucketPos, bucketRot, weight, realGroundPoint, 1f);//heightFactor); // 传地面位置和高度因子 } // 应用铲斗弧度效果 void ApplyBucketCurvature(Vector3 bucketPos, Quaternion bucketRot, float weight, Vector3 groundPoint, float heightFactor) { // 使用GenTestForDigging的采样步数,提升精细度 int steps = genTestForDigging.pileSampleSteps; for (int i = 1; i <= steps; i++) { float distance = (i / (float)steps) * bucketSize.z * 1.2f; // 扩大采样范围 float curve = Mathf.Sin(i / (float)steps * Mathf.PI * 0.8f) * bucketCurvature; // 平缓的曲线 // 新增:圆锥状权重衰减(核心) float radialFactor = 1 - (i / (float)steps); // 径向衰减(从中心到边缘) // 平顶处理:顶部一定比例内权重保持1 radialFactor = i / (float)steps < genTestForDigging.pileFlatTopRatio ? 1f : radialFactor; // 平滑衰减:用余弦曲线,减少棱角 radialFactor = Mathf.Cos(radialFactor * Mathf.PI * 0.5f); // 随机偏移:降低幅度,避免形状诡异 Vector3 randomOffset = Vector3.zero; if (weight < 0) { randomOffset = new Vector3( Random.Range(-genTestForDigging.pileRandomness * 0.5f, genTestForDigging.pileRandomness * 0.5f), 0, Random.Range(-genTestForDigging.pileRandomness * 0.5f, genTestForDigging.pileRandomness * 0.5f) ); } // 基于地面位置计算偏移,避免空中生成 Vector3 dirToGround = (groundPoint - bucketPos).normalized; Vector3 offset = bucketRot * (new Vector3(0, -curve, -distance) + randomOffset); Vector3 curvedPoint = groundPoint + offset * 0.8f; // 贴合地面 // 综合权重:径向+高度+距离衰减 float falloff = radialFactor * heightFactor * (1 - (i / (float)steps) * 0.5f); if (weight < 0) { falloff *= (1 + gravityInfluence * (1 - i / (float)steps)); } // 调整尺寸:从中心到边缘逐渐缩小,形成圆锥 Vector3 scaledSize = bucketSize * (falloff * 0.8f + 0.2f); genTestForDigging.Terraform( curvedPoint, weight * falloff, scaledSize, bucketRot.eulerAngles ); } } }using System; using UnityEngine; using System.Collections; [RequireComponent(typeof(Collider))] public class BucketDigControllerForDigging : MonoBehaviour { [Header(“基础设置”)] public Transform bucketTip;// 铲斗前端中心点(粒子/石块生成起点) public LayerMask groundMask;// 地面层 public LayerMask soilPileMask;// 土堆层(可选,单独区分土堆) [Header("挖/填角度阈值(原正常参数)")] [Range(-1f, 1f)] public float digAngleThreshold = -0.5f; [Range(-1f, 0f)] public float fillAngleThreshold = -0.9f; public float requiredMoveSpeed = 0.01f; public float digCooldown = 0.2f; [Header("挖掘参数(原正常参数)")] public float digRadius = 0.5f;// 挖掘半径 public float digDepth = 0.15f;// 挖掘深度 [Header("土壤参数(原正常逻辑)")] public float maxSoilCapacity = 1f; public float soilPerDig = 0.2f; public float soilPerFill = 0.2f; public GameObject[] soilVisual; [Header("倾倒粒子效果(垂直发射)")] public ParticleSystem fillParticles; // 仅保留倾倒粒子 public float particleEmitRate = 80f; // 密集发射 public float particleStartSize = 0.3f; // 粒子大小 public float particleStartSpeed = 4f; // 粒子速度 public float particleLifetime = 1.5f; // 生命周期 [Tooltip("粒子垂直扩散角度(0=纯垂直,建议0-5)")] public float particleVerticalSpread = 0f; // 垂直扩散角度 [Tooltip("调试:强制显示粒子(绕过判定)")] public bool forceShowParticle = false; // 保留该字段(Inspector可见) [Header("石块掉落配置(完整恢复)")] public GameObject[] rockPrefabs; [Range(3, 8)] public int minRocksPerDump = 3; [Range(8, 15)] public int maxRocksPerDump = 8; public float rockSpawnRadius = 0.8f; public float rockDropForce = 12f; public float rockMass = 1.5f; public float rockDrag = 0.05f; public float rockEmbedDepthMin = 0.05f; public float rockEmbedDepthMax = 0.15f; public float rockRandomRotationRange = 180f; public float spawnInterval = 0.05f; // 私有变量 private float lastRockSpawnTime = -1f; private float currentSoil = 0f; private float digTimer = 0f; private Vector3 lastTipPos; private bool isParticleActive = false; // 记录粒子是否已激活 [Header("引用")] public TerraformerForDigging terraformerForDigging; public ExcavatorJointController excavatorJointController; void Start() { if (bucketTip == null) { Debug.LogError("指定 bucketTip(铲斗前端中心点)!"); enabled = false; return; } lastTipPos = bucketTip.position; currentSoil = 0f; isParticleActive = false; Debug.Log($"[初始化] 土壤容量:{currentSoil} | 粒子激活状态:{isParticleActive}"); // 初始化粒子(垂直发射) InitParticleSystem(); if (rockPrefabs == null || rockPrefabs.Length == 0) { Debug.LogError("[石块配置] 添加石块预制体!"); } else { Debug.Log($"[石块配置] 已加载 {rockPrefabs.Length} 个预制体"); } } #region 粒子初始化(核心:垂直地面发射) private void InitParticleSystem() { if (fillParticles == null) { Debug.LogError("[粒子初始化] fillParticles赋值!"); return; } // 【修改1】不再解绑父物体,直接绑定到铲斗前端,自动同步位置 fillParticles.transform.SetParent(bucketTip); fillParticles.transform.localPosition = Vector3.zero; fillParticles.transform.localRotation = Quaternion.Euler(90f, 0f, 0f); // 强制重置粒子系统所有状态 fillParticles.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); fillParticles.Clear(true); // 粒子核心配置 var mainModule = fillParticles.main; mainModule.loop = true; mainModule.duration = 0f; mainModule.startLifetime = particleLifetime; mainModule.startSpeed = particleStartSpeed; mainModule.startSize = particleStartSize; mainModule.gravityModifier = 1f; // 纯重力垂直下落 mainModule.maxParticles = 1000; mainModule.stopAction = ParticleSystemStopAction.None; mainModule.playOnAwake = false; // Shape模块:纯垂直发射 var shapeModule = fillParticles.shape; shapeModule.shapeType = ParticleSystemShapeType.Cone; shapeModule.angle = particleVerticalSpread; // 0=纯垂直无扩散 shapeModule.radius = 0.1f; // 缩小半径,强化垂直集中 shapeModule.rotation = Vector3.zero; shapeModule.position = Vector3.zero; // 发射模块初始化 var emissionModule = fillParticles.emission; emissionModule.enabled = false; emissionModule.rateOverTime = particleEmitRate; // 禁用可能导致粒子偏移的模块 var velocityOverLifetime = fillParticles.velocityOverLifetime; velocityOverLifetime.enabled = false; var forceOverLifetime = fillParticles.forceOverLifetime; forceOverLifetime.enabled = false; Debug.Log("[粒子初始化] 完成:修复垂直发射+父物体绑定"); } #endregion #region 核心交互判定(保留forceShowParticle逻辑) private void CheckBucketInteraction() { if (bucketTip == null) return; Vector3 tipPos = bucketTip.position; float moveSpeed = (tipPos - lastTipPos).magnitude / Time.fixedDeltaTime; lastTipPos = tipPos; // 原判定逻辑 float dot = Vector3.Dot(bucketTip.forward, Vector3.down); bool isMoving = moveSpeed > requiredMoveSpeed; bool isTouchingGround = Physics.OverlapBox( tipPos, bucketTip.lossyScale * 0.5f, bucketTip.rotation, groundMask ).Length > 0; bool hasSoil = currentSoil > 0.05f; bool isDumpDirection = excavatorJointController != null ? excavatorJointController.direction < 0 : true; bool isDumpAngle = dot < fillAngleThreshold; bool isDumping = isDumpDirection && isDumpAngle && isMoving && hasSoil; // 【新增】强化调试日志,确认每次倾倒判定是否生效 Debug.Log($"[判定] 移动速度:{moveSpeed:F2}(阈值{requiredMoveSpeed}) | 角度:{dot:F2}(阈值{fillAngleThreshold}) | 有土壤:{hasSoil} | 倾倒方向:{isDumpDirection} | 最终倾倒判定:{isDumping}"); // 保留forceShowParticle逻辑(通过Inspector勾选触发,无按钮) if (forceShowParticle) { ShowFillParticles(); return; } // 挖掘逻辑(原逻辑) if (excavatorJointController != null && excavatorJointController.direction > 0 && isTouchingGround && dot > digAngleThreshold && isMoving && currentSoil < maxSoilCapacity) { terraformerForDigging.ChangeDigging(true); terraformerForDigging.HandleTerraformEvent(bucketTip); currentSoil += soilPerDig * Time.deltaTime * 5; currentSoil = Mathf.Clamp(currentSoil, 0f, maxSoilCapacity); HideFillParticles(); } // 倾倒逻辑(原逻辑) else if (isDumping) { terraformerForDigging.ChangeFilling(true); terraformerForDigging.HandleTerraformEvent(bucketTip); // 土壤消耗 currentSoil -= soilPerFill * Time.deltaTime * 5; currentSoil = Mathf.Clamp(currentSoil, 0f, maxSoilCapacity); // 触发粒子 ShowFillParticles(); // 触发石块 SpawnRocksIfNeeded(); } // 无操作状态 else { if (terraformerForDigging != null) { terraformerForDigging.ChangeDigging(false); terraformerForDigging.ChangeFilling(false); } HideFillParticles(); } } #endregion #region 粒子控制(垂直发射)【核心修改区】 private void ShowFillParticles() { if (fillParticles == null) return; // 强制同步旋转(防止父物体旋转偏移) fillParticles.transform.localRotation = Quaternion.Euler(90f, 0f, 0f); // 启用发射模块 var emissionModule = fillParticles.emission; emissionModule.enabled = true; emissionModule.rateOverTime = particleEmitRate; // 播放粒子系统(强制立即播放) if (!fillParticles.isPlaying) { fillParticles.Play(true); // 立即播放(含子物体) Debug.Log($"[粒子触发] 播放粒子系统 | 发射率:{particleEmitRate} | 位置:{fillParticles.transform.position}"); } // 更新粒子激活状态 isParticleActive = true; } private void HideFillParticles() { if (fillParticles == null) return; // 【修改2】禁用发射模块 var emissionModule = fillParticles.emission; emissionModule.enabled = false; // 【修改3】无论粒子数量多少,强制停止并清空(关键修复) if (fillParticles.isPlaying) { fillParticles.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); fillParticles.Clear(true); Debug.Log("[粒子停止] 强制停止并清空粒子,重置状态"); } // 更新粒子激活状态 isParticleActive = false; } #endregion #region 石块生成(原逻辑) private void SpawnRocksIfNeeded() { if (rockPrefabs == null || rockPrefabs.Length == 0) return; if (Time.time - lastRockSpawnTime < 0.5f) return; StartCoroutine(SpawnRocksWithInterval()); lastRockSpawnTime = Time.time; Debug.Log($"[石块生成] 触发:{Time.time:F2}"); } private IEnumerator SpawnRocksWithInterval() { int rockCount = UnityEngine.Random.Range(minRocksPerDump, maxRocksPerDump + 1); for (int i = 0; i < rockCount; i++) { SpawnSingleRock(); yield return new WaitForSeconds(spawnInterval); } Debug.Log($"[石块生成] 本次生成 {rockCount} 个"); } private void SpawnSingleRock() { int randomIdx = UnityEngine.Random.Range(0, rockPrefabs.Length); GameObject rockPrefab = rockPrefabs[randomIdx]; if (rockPrefab == null) { Debug.LogWarning($"[石块生成] 索引{randomIdx}预制体为空!"); return; } Vector3 randomOffset = new Vector3( UnityEngine.Random.Range(-rockSpawnRadius, rockSpawnRadius), UnityEngine.Random.Range(0.05f, 0.15f), UnityEngine.Random.Range(-rockSpawnRadius, rockSpawnRadius) ); Vector3 spawnPos = bucketTip.position + randomOffset; GameObject rock = Instantiate(rockPrefab, spawnPos, Quaternion.identity); rock.name = $"Rock_{Time.time}_{UnityEngine.Random.Range(1000, 9999)}"; Rigidbody rb = rock.GetComponent<Rigidbody>(); if (rb == null) { rb = rock.AddComponent<Rigidbody>(); } rb.mass = rockMass; rb.drag = rockDrag; rb.angularDrag = 0.1f; rb.useGravity = true; rb.isKinematic = false; rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic; rb.interpolation = RigidbodyInterpolation.Interpolate; Vector3 dumpForce = (bucketTip.forward * 0.8f + Vector3.down * 1.2f) * rockDropForce; rb.AddForce(dumpForce, ForceMode.Impulse); rb.rotation = Quaternion.Euler( UnityEngine.Random.Range(-rockRandomRotationRange, rockRandomRotationRange), UnityEngine.Random.Range(-rockRandomRotationRange, rockRandomRotationRange), UnityEngine.Random.Range(-rockRandomRotationRange, rockRandomRotationRange) ); StartCoroutine(AdjustRockPositionAfterLanding(rock)); } private IEnumerator AdjustRockPositionAfterLanding(GameObject rock) { Rigidbody rb = rock.GetComponent<Rigidbody>(); if (rb == null) yield break; float waitTime = 0f; while (waitTime < 2f && rb.velocity.magnitude > 0.05f) { waitTime += Time.deltaTime; yield return null; } if (rock == null || rb.isKinematic) yield break; RaycastHit hit; LayerMask targetMask = soilPileMask != 0 ? soilPileMask : groundMask; if (Physics.Raycast(rock.transform.position + Vector3.up * 0.1f, Vector3.down, out hit, 0.5f, targetMask)) { float embedDepth = UnityEngine.Random.Range(rockEmbedDepthMin, rockEmbedDepthMax); rock.transform.position = hit.point + Vector3.up * (rock.transform.localScale.y / 2 - embedDepth); rb.velocity = Vector3.zero; rb.angularVelocity = Vector3.zero; } } #endregion #region FixedUpdate+可视化(原逻辑) void FixedUpdate() { digTimer -= Time.fixedDeltaTime; CheckBucketInteraction(); if (bucketTip == null) return; UpdateSoilVisual(); } private void UpdateSoilVisual() { if (soilVisual == null) return; float percent = currentSoil / maxSoilCapacity; soilVisual[3].SetActive(percent > 0.01f); soilVisual[2].SetActive(percent > 0.35f); soilVisual[1].SetActive(percent > 0.5f); soilVisual[0].SetActive(percent > 0.8f); } #endregion #if UNITY_EDITOR void OnDrawGizmosSelected() { if (bucketTip == null) return; // 铲斗位置标记 Gizmos.color = Color.green; Gizmos.DrawWireSphere(bucketTip.position, 0.1f); // 粒子垂直方向标记(色射线) Gizmos.color = Color.red; Gizmos.DrawRay(bucketTip.position, Vector3.down * 1.5f); // 石块生成范围 Gizmos.color = Color.cyan; Gizmos.DrawWireSphere(bucketTip.position, rockSpawnRadius); } #endif }using UnityEngine; using System.Collections.Generic; using Unity.Mathematics; public class Chunk { public Vector3 centre; public float size; public Mesh mesh; public ComputeBuffer pointsBuffer; int numPointsPerAxis; public MeshFilter filter; MeshRenderer renderer; MeshCollider collider; public bool terra; public Vector3Int id; // Mesh processing Dictionary<int2, int> vertexIndexMap; List<Vector3> processedVertices; List<Vector3> processedNormals; List<int> processedTriangles; // 顶点焊接字典(需每次CreateMesh重置) private Dictionary<Vector3, int> vertexMap = new Dictionary<Vector3, int>(); public Chunk(Vector3Int coord, Vector3 centre, float size, int numPointsPerAxis, GameObject meshHolder) { this.id = coord; this.centre = centre; this.size = size; this.numPointsPerAxis = numPointsPerAxis; mesh = new Mesh(); mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; int numPointsTotal = numPointsPerAxis * numPointsPerAxis * numPointsPerAxis; ComputeHelper.CreateStructuredBuffer<PointData>(ref pointsBuffer, numPointsTotal); // Mesh rendering and collision components filter = meshHolder.AddComponent<MeshFilter>(); renderer = meshHolder.AddComponent<MeshRenderer>(); filter.mesh = mesh; collider = renderer.gameObject.AddComponent<MeshCollider>(); vertexIndexMap = new Dictionary<int2, int>(); processedVertices = new List<Vector3>(); processedNormals = new List<Vector3>(); processedTriangles = new List<int>(); } // 新增:焊接接近的顶点(阈值0.01f,越小越精细) private int GetWeldedVertexIndex(Vector3 position, float weldThreshold = 0.01f) { // 四舍五入到阈值精度,避免浮点误差 Vector3 key = new Vector3( Mathf.Round(position.x / weldThreshold) * weldThreshold, Mathf.Round(position.y / weldThreshold) * weldThreshold, Mathf.Round(position.z / weldThreshold) * weldThreshold ); if (vertexMap.ContainsKey(key)) { return vertexMap[key]; // 返回已存在的顶点索引 } vertexMap.Add(key, vertexMap.Count); // 新增顶点 return vertexMap.Count - 1; } // 核心修改:整合顶点焊接的CreateMesh方法 public void CreateMesh(VertexData[] vertexData, int numVertices, bool useFlatShading) { // ========== 核心优化1:强制禁用平面着色 + 重置焊接字典 ========== useFlatShading = false; vertexMap.Clear(); // 每次生成Mesh都清空焊接字典,避免旧数据干扰 vertexIndexMap.Clear(); processedVertices.Clear(); processedNormals.Clear(); processedTriangles.Clear(); // ========== 核心优化2:替换原有顶点逻辑,使用焊接索引 ========== for (int i = 0; i < numVertices; i++) { VertexData data = vertexData[i]; // 第一步:获取焊接后的顶点索引(核心修改) int weldedVertexIndex = GetWeldedVertexIndex(data.position); // 第二步:确保顶点/法线列表长度足够,填充空值(避免索引越界) while (processedVertices.Count <= weldedVertexIndex) { processedVertices.Add(Vector3.zero); processedNormals.Add(Vector3.zero); } // 第三步:赋值顶点和法线(覆盖空值/更新已有值) processedVertices[weldedVertexIndex] = data.position; processedNormals[weldedVertexIndex] = data.normal; // 第四步:添加焊接后的索引到三角面(替换原有triangleIndex逻辑) processedTriangles.Add(weldedVertexIndex); } // ========== 原有Mesh赋值逻辑(保留,仅微调) ========== collider.sharedMesh = null; // 先清空碰撞体,避免赋值冲突 mesh.Clear(); mesh.SetVertices(processedVertices); mesh.SetTriangles(processedTriangles, 0, true); // ========== 核心优化3:强制使用预计算的平滑法线 ========== if (useFlatShading) { mesh.RecalculateNormals(); // 禁用平面着色后不会执行此分支 } else { mesh.SetNormals(processedNormals); // 用体素计算的平滑法线 mesh.RecalculateTangents(); // 优化光影过渡 mesh.RecalculateBounds(); // 确保网格边界正确 } // 显式赋值MeshFilter,确保修改生效 filter.mesh = mesh; collider.sharedMesh = mesh; } public struct PointData { public Vector3 position; public Vector3 normal; public float density; } public void AddCollider() { collider.sharedMesh = mesh; } public void SetMaterial(Material material) { renderer.material = material; } public void Release() { ComputeHelper.Release(pointsBuffer); } public void DrawBoundsGizmo(Color col) { Gizmos.color = col; Gizmos.DrawWireCube(centre, Vector3.one * size); } }#pragma kernel CSMain RWTexture3D EditTexture; int size; int3 brushCentre; float deltaTime; float weight; float smoothness; float brushStrength; float3 brushDimensions; float4x4 brushMatrix; float ellipsoidDistance(float3 worldPos, float3 centre, float3 dimensions, float4x4 brushMatrix) { float3 relativePos = worldPos - centre; float3 localPos = mul(relativePos, (float3x3)brushMatrix); float3 scaled = localPos / (dimensions * 0.5); return length(scaled); } [numthreads(8, 8, 8)] void CSMain(uint3 id : SV_DispatchThreadID) { if (id.x >= uint(size) || id.y >= uint(size) || id.z >= uint(size)) return; float3 worldPos = float3(id.x, id.y, id.z); float dist = ellipsoidDistance(worldPos, brushCentre, brushDimensions, brushMatrix); if (dist <= 1.0f) { // float falloff = exp(-dist * smoothness); // float change = weight * brushStrength * falloff * deltaTime; // change = clamp(change, -0.2, 0.2); EditTexture[id] += change; } }#pragma kernel Blur RWTexture3D Source; RWTexture3D Result; int blurRadius; int textureSize; int brushRadius; int3 brushCentre; [numthreads(8, 8, 8)] void Blur(int3 id : SV_DispatchThreadID) { id = id + brushCentre; if (id.x >= textureSize || id.y >= textureSize || id.z >= textureSize) { return; } float sum = 0; float totalWeight = 0; for (int z = -blurRadius; z <= blurRadius; z++) { for (int y = -blurRadius; y <= blurRadius; y++) { for (int x = -blurRadius; x <= blurRadius; x++) { float dst = length(float3(x, y, z)) / blurRadius; float weight = pow(2.5, -10 * dst); int3 samplePos = id + int3(x, y, z); samplePos = max(0, min(samplePos, textureSize)); sum += Source[samplePos] * weight; totalWeight += weight; } } } //int n = 2 * blurRadius + 1; //int iterations = n * n * n; float average = sum / totalWeight; Result[id] = average; //Result[id] = Source[id]; } #pragma kernel ProcessCube #include “/Includes/MarchTables.compute” struct Vertex { float3 position; float3 normal; int2 id; }; struct Triangle { Vertex vertexC; Vertex vertexB; Vertex vertexA; }; // AppendStructuredBuffer triangles; RWTexture3D DensityTexture; int textureSize; int numPointsPerAxis; float planetSize; float isoLevel; float3 chunkCoord; float3 coordToWorld(int3 coord) { return (coord / (textureSize - 1.0) - 0.5f) * planetSize; } int indexFromCoord(int3 coord) { coord = coord - int3(chunkCoord); return coord.z * numPointsPerAxis * numPointsPerAxis + coord.y * numPointsPerAxis + coord.x; } float sampleDensity(int3 coord) { coord = max(0, min(coord, textureSize)); return DensityTexture[coord]; } float3 calculateNormal(int3 coord) { int3 offsetX = int3(1, 0, 0); int3 offsetY = int3(0, 1, 0); int3 offsetZ = int3(0, 0, 1); float dx = sampleDensity(coord + offsetX) - sampleDensity(coord - offsetX); float dy = sampleDensity(coord + offsetY) - sampleDensity(coord - offsetY); float dz = sampleDensity(coord + offsetZ) - sampleDensity(coord - offsetZ); return normalize(float3(dx, dy, dz)); } // Calculate the position of the vertex // The position lies somewhere along the edge defined by the two corner points. // Where exactly along the edge is determined by the values of each corner point. Vertex createVertex(int3 coordA, int3 coordB) { float3 posA = coordToWorld(coordA); float3 posB = coordToWorld(coordB); float densityA = sampleDensity(coordA); float densityB = sampleDensity(coordB); // Interpolate between the two corner points based on the density float t = (isoLevel - densityA) / (densityB - densityA); float3 position = posA + t * (posB - posA); // Normal: float3 normalA = calculateNormal(coordA); float3 normalB = calculateNormal(coordB); float3 normal = normalize(normalA + t * (normalB - normalA)); // ID int indexA = indexFromCoord(coordA); int indexB = indexFromCoord(coordB); // Create vertex Vertex vertex; vertex.position = position; vertex.normal = normal; vertex.id = int2(min(indexA, indexB), max(indexA, indexB)); return vertex; } [numthreads(8, 8, 8)] void ProcessCube(int3 id : SV_DispatchThreadID) { int numCubesPerAxis = numPointsPerAxis - 1; if (id.x >= numCubesPerAxis || id.y >= numCubesPerAxis || id.z >= numCubesPerAxis) { return; } int3 coord = id + int3(chunkCoord); // Calculate coordinates of each corner of the current cube int3 cornerCoords[8]; cornerCoords[0] = coord + int3(0, 0, 0); cornerCoords[1] = coord + int3(1, 0, 0); cornerCoords[2] = coord + int3(1, 0, 1); cornerCoords[3] = coord + int3(0, 0, 1); cornerCoords[4] = coord + int3(0, 1, 0); cornerCoords[5] = coord + int3(1, 1, 0); cornerCoords[6] = coord + int3(1, 1, 1); cornerCoords[7] = coord + int3(0, 1, 1); // Calculate unique index for each cube configuration. // There are 256 possible values (cube has 8 corners, so 2^8 possibilites). // A value of 0 means cube is entirely inside the surface; 255 entirely outside. // The value is used to look up the edge table, which indicates which edges of the cube the surface passes through. int cubeConfiguration = 0; for (int i = 0; i < 8; i++) { // Think of the configuration as an 8-bit binary number (each bit represents the state of a corner point). // The state of each corner point is either 0: above the surface, or 1: below the surface. // The code below sets the corresponding bit to 1, if the point is below the surface. if (sampleDensity(cornerCoords[i]) < isoLevel) { cubeConfiguration |= (1 << i); } } // Get array of the edges of the cube that the surface passes through. int edgeIndices[] = triangulation[cubeConfiguration]; // Create triangles for the current cube configuration for (i = 0; i < 16; i += 3) { // If edge index is -1, then no further vertices exist in this configuration if (edgeIndices[i] == -1) // Get indices of the two corner points defining the edge that the surface passes through. // (Do this for each of the three edges we're currently looking at). int edgeIndexA = edgeIndices[i]; int a0 = cornerIndexAFromEdge[edgeIndexA]; int a1 = cornerIndexBFromEdge[edgeIndexA]; int edgeIndexB = edgeIndices[i + 1]; int b0 = cornerIndexAFromEdge[edgeIndexB]; int b1 = cornerIndexBFromEdge[edgeIndexB]; int edgeIndexC = edgeIndices[i + 2]; int c0 = cornerIndexAFromEdge[edgeIndexC]; int c1 = cornerIndexBFromEdge[edgeIndexC]; // Calculate positions of each vertex. Vertex vertexA = createVertex(cornerCoords[a0], cornerCoords[a1]); Vertex vertexB = createVertex(cornerCoords[b0], cornerCoords[b1]); Vertex vertexC = createVertex(cornerCoords[c0], cornerCoords[c1]); // Create triangle Triangle tri; tri.vertexA = vertexC; tri.vertexB = vertexB; tri.vertexC = vertexA; triangles.Append(tri); } } #pragma kernel CSMain // 修正包含路径 - 使用相对路径 #include “./Includes/Noise.compute” RWTexture3D DensityTexture; int textureSize; float planetSize; float noiseScale; float noiseHeightMultiplier; bool generateCube = true; // 控制是否生成立方体 float cubeSize; // 立方体大小 // 简单噪声函数替代(如果找不到snoise函数) float simpleNoise(float3 pos) { return frac(sin(dot(pos, float3(12.9898, 78.233, 45.5432))) * 43758.5453); } float fbm(int numLayers, float lacunarity, float persistence, float scale, float3 pos) { float noise = 0; float frequency = scale / 100; float amplitude = 1; for (int i = 0; i < numLayers; i++) { // 使用简单噪声替代snoise(如果找不到) ifdef SNOISE_AVAILABLE float n = 1 - abs(snoise(pos * frequency) * 2 - 1); #else float n = simpleNoise(pos * frequency); #endif noise += n * amplitude; amplitude *= persistence; frequency *= lacunarity; } return noise; } // 立方体符号距离函数 float cubeSDF(float3 p, float size) { float3 q = abs(p) - size / 2; return length(max(q, 0)) + min(max(q.x, max(q.y, q.z)), 0); } float calculateDensity(int3 cell) { float3 worldPos = (cell / float(textureSize - 1.0) - 0.5f) * planetSize; float density = 0; if (generateCube) { // 使用立方体SDF计算密度值 density = cubeSDF(worldPos, cubeSize); } else { // 原始的球体生成代码 float halfS = planetSize / 2; float maxD = length(float3(halfS, halfS, halfS)); float fudge = 1; density = length(worldPos) / (maxD + fudge) - 0.5; // 添加噪声 float noise = fbm(6, 2, 0.5, noiseScale, worldPos) * noiseHeightMultiplier; density += noise; } return density; } [numthreads(8, 8, 8)] void CSMain(int3 id : SV_DispatchThreadID) { if (id.x >= textureSize || id.y >= textureSize || id.z >= textureSize) { return; } float density = 0; const int b = 1; if (id.x >= textureSize - b || id.y >= textureSize - b || id.z >= textureSize - b) { density = 1; } else if (id.x <= b || id.y <= b || id.z <= b) { density = 1; } else { density = calculateDensity(id); } DensityTexture[id] = density; }
最新发布
12-24
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值